美文网首页
vue.js如何演示思维导图

vue.js如何演示思维导图

作者: 马克约瑟 | 来源:发表于2024-03-06 18:05 被阅读0次

背景

深交所在 2024 年请技术口全体人员参加名为“程序员的浪漫——新春送祝福”的活动。真不错!我喜欢这个活动,它很好地激发了我创作的热情。

经过策划,我决定用 vue.js 来演示思维导图,以全面宣传深交所。问题来了,如何实现?我搜索了全网,实在找不到一篇教程,教人如何用 vue.js 或 JavaScript 来演示思维导图中的每一个节点。面对这样的困难,我想过要不裁剪出每一个节点来播放吧?这样做起来就简单多了。哦,不!这样做太投机取巧了!毕竟我想夺取实施难度大的巧夺天工奖呢!因此,我决定做全网第一人,公开发表 vue.js 如何演示一张思维导图。

思维导图

下面这张思维导图,告诉我们深交所所有的宣传标语。我需要在活动作品中演示它的每一个节点。

深交所宣传标语

思路

首先,要知道这张图非常大,况且它只能被放在页面中一个指定的组件或容器中。其次,存放和演示思维导图的容器的尺寸是有限的,无法 100% 显示一个节点。因此,演示的时候,思维导图应缩放到特定的尺寸,使得容器恰好只显示一个节点,而其余节点则被隐藏或遮挡了。

我可以利用以下 CSS 特性(关注粗体字):

  • background-repeat:定义背景图像的重复方式。背景图像可以沿着水平轴,垂直轴,两个轴重复,或者根本不重复
  • background-size:设置背景图片大小。图片可以保有其原有的尺寸,或者拉伸到新的尺寸,或者在保持其原有比例的同时缩放到元素的可用空间的尺寸
  • background-position-x:设置水平方向的位置。
  • background-position-y:用于设置初始状态时背景图片在垂直方向上的位置。

我应先获取每个节点的位置和尺寸,所需的数据如下:

属性名 属性含义
x 横坐标
y 纵坐标
w 宽度
h 高度

接下来,用伪代码描述下放置和缩放图片的(核心)思路:

if 节点宽度 <= 容器最大宽度
   容器宽度 = 节点宽度
else
   容器宽度 = 容器最大宽度

演示缩放比 = 容器宽度 / 节点宽度
容器高度 = 节点高度 * 演示缩放比

if 容器高度 > 容器最大高度
   容器宽度 = 容器宽度 * 容器最大高度 / 容器高度
   演示缩放比 = 容器宽度 / 节点宽度

容器高度 = 节点高度 * 演示缩放比

background-size = 图片原始宽度 / 节点宽度
background-position-x = 0 - 节点横坐标 * 演示缩放比
background-position-y = 0 - 节点纵坐标 * 演示缩放比

实现

1、底层依赖

我选择的vue.js的版本是 2.7.7

2、容器

要知道,我们既可能在 14 寸电脑显示器上演示思维导图,也可能在 12 寸电脑显示器上演示,也可能在大屏电视机上演示,所以,我打算使容器的尺寸适应不同的显示器,小屏就小图显示,大屏则大图显示。

看以下代码:

<template>
  <div id="imgContainer" :style="imgContainerStyle"></div>
</template>

<script>
export default {
  name: 'MindMapping',
  data() {
    return {
      // 图片的原始尺寸
      imgSize: {
        w: 4195,
        h: 2037
      },
      imgContainerStyle: {
        top: 0,
        bottom: 0,
        left: 0,
        right: 0,
        margin: 'auto',
        width: '0px',
        height: '0px',
        backgroundSize: 'cover'
      }
    }
  },
  computed: {
    // 图片最大的宽度,希望不要被其它元素遮挡
    getMaxShowWidth() {
      return window.innerWidth - 605;
    },
    // 图片最大的高度,希望不要越出浏览器
    getMaxShowHeight() {
      return Math.floor(window.innerHeight * 0.7);
    }
  },
  mounted() {
    // 计算思维导图的尺寸,应保持图片的宽高比,否则会变形
    let h = Math.ceil(this.getMaxShowWidth * (this.imgSize.h / this.imgSize.w));
    this.imgContainerStyle.width = `${this.getMaxShowWidth}px`;
    this.imgContainerStyle.height = `${h}px`;
  }
}
</script>

<style scoped>
#imgContainer {
  display: inline-block;
  position: absolute;
  background-image: url(images/深交所宣传标语.v3.png);
  background-repeat: no-repeat;
}
</style>

一点点解释下:

  • 我使容器以绝对布局显示,这样就可以水平和垂直居中显示了。
  • 我使用 background-xxx 的方式,这样有助于只显示图片的一部分。
  • getMaxShowWidthgetMaxShowHeight 是我自己的计算,你可以用其它方式计算。
  • 首次加载思维导图时,计算容器最多可以多少像素来显示,然后为了保持宽高比,计算容器的高度,最后使图片铺满整个容器。

效果图:

首次加载思维导图

3、准备每个节点的坐标和尺寸信息

正如思路中所阐述的,我需要准备每个节点的坐标和尺寸信息。ImageGlass软件可以起到作用。

信息如下所示:

// 思维导图的演示路径
showPath: [{// 中心主题
    x: 1870, y: 850, w: 837, h: 258
}, {// 价值观
    x: 2640, y: 0, w: 800, h: 230
}, {// 使命
    x: 2640, y: 178, w: 800, h: 280
}, {// 愿景
    x: 2640, y: 395, w: 1000, h: 270
}, {// 深交所“十四五”发展战略规划与“优一”建设纲要的总体原则
    x: 2640, y: 623, w: 1440, h: 555
}, {// 深交所“十四五”发展战略规划的主要目标
    x: 2640, y: 1164, w: 1440, h: 492
}, {// “优一”建设纲要的主要目标
    x: 2640, y: 1662, w: 1558, h: 375
}, {// 十四五IT发展战略目标
    x: 0, y: 0, w: 1890, h: 2037
}, {// 战略目标之使命&愿景
    x: 326, y: 140, w: 978, h: 586
}, {// 战略目标之原则&策略
    x: 244, y: 718, w: 982, h: 660
}, {// 战略目标之战略目标
    x: 42, y: 1364, w: 1188, h: 256
}, {// 战略目标之重点任务
    x: 432, y: 1600, w: 794, h: 390
}]

我把这些信息放在数组里,这样我就可以使程序依次演示每个节点了。

4、实现伪代码

思路中的伪代码是此篇文章的核心算法,我需要实现它。

getShowProp(x, y, w, h) {
    let displayWidth, displayHeight, displayX, displayY, scale;
    if (w > this.getMaxShowWidth) {
        displayWidth = this.getMaxShowWidth;
    } else {
        displayWidth = w;
    }
    scale = Math.round(displayWidth / w * 10000) / 10000;
    displayHeight = Math.floor(h * scale);
    if (displayHeight > this.getMaxShowHeight) {
        displayWidth = parseInt(displayWidth * this.getMaxShowHeight / displayHeight);
        scale = Math.round(displayWidth / w * 10000) / 10000;
    }
    displayHeight = Math.floor(h * scale);
    displayX = 0 - Math.floor(x * scale);
    displayY = 0 - Math.floor(y * scale);
    return {
        x: displayX,
        y: displayY,
        w: displayWidth,
        h: displayHeight
    };
}

有了这个函数,我就可以用它的返回值来设置容器的宽度、高度和 background-position 了。等等,background-size 值怎么设置?还需要以下实现代码:

getBackgroundSize(w) {
    let scale = Math.floor(this.imgSize.w / w * 100);
    return `${scale}%`;
}

我只需要把节点的宽度值传给这个函数,就可以用它的返回值设置 background-size 了。

5、演示函数

有了以上核心实现,我就可以执行起来了。此时我需要执行核心代码的代码——演示函数。

// 演示节点
internalShow() {
    const next = this.showPath[this.nextPath];
    const showProp = this.getShowProp(next.x, next.y, next.w, next.h);

    // 使节点进场
    this.$nextTick(() => {
        this.imgContainerStyle.width = `${showProp.w}px`;
        this.imgContainerStyle.height = `${showProp.h}px`;
        this.imgContainerStyle.backgroundPosition = `${showProp.x}px ${showProp.y}px`;
        this.imgContainerStyle.backgroundSize = this.getBackgroundSize(next.w);
        this.imgContainerStyle.left = 0;
        this.imgContainerStyle.right = 0;
        this.imgContainerStyle.top = 0;
        this.imgContainerStyle.bottom = 0;
        this.imgContainerStyle.margin = 'auto';
    });
}

其中,nextPath 是演示节点的指示器,它在 data 里设置。初始设置如下:

nextPath: -1

到此,我可以迫不及待地测试一下。我可以在 mounted 里添加如下代码:

setTimeout(() => {
    this.nextPath = 0;
    this.internalShow();
}, 1000);

以上代码表示页面加载 1 秒后,就演示第 1 个节点。亲测成功!

6、完善演示函数

我希望程序能像 PPT 一样,既可以演示下一个节点,也可以演示上一个节点。而且,我还需要检查 nextPath 值,使它不能越界,也就是说,当演示到最后一个节点时,就不能再往下演示了,当回退到第一个节点时,就不能再回退了。于是,有了以下代码:

// 演示下一个节点
showNext() {
    // 如果演示到最后一个节点,就不再往后演示
    if (++this.nextPath === this.showPath.length) {
        this.nextPath--;
        return;
    }
    this.internalShow();
},
// 演示上一个节点
showPrevious() {
    // 如果后退到第一个节点,就不再后退
    if (--this.nextPath < 0) {
        this.nextPath++;
        return;
    }
    this.internalShow();
}

mounted 里使用以下代码测试下:

setTimeout(() => {
    this.showNext();
    setTimeout(() => {
        this.showNext();
        setTimeout(this.showPrevious, 500);
    }, 500);
}, 1000);

7、键盘指令

有没有发现,我刚陷入了回调地狱中?因为 setTimeout 嵌套太复杂了,让人抓狂!

所以,为了演示每个节点,我决定使用键盘指令——按下方向左右键——来指示程序演示,所以我应绑定键盘事件。

mounted 里加入以下代码:

window.addEventListener('keyup', event => {
    if ('ArrowRight' === event.key) {
        // 按下`right`键时,使思维导图向后演示
        this.showNext();
    } else if ('ArrowLeft' === event.key) {
        // 按下`left`键时,使思维导图向前演示
        this.showPrevious();
    }
});

到此,按下方向右键时,程序就演示下一个节点,按下方向左键时,程序就回到上一个节点。

8、最终效果

最终效果

更多

嘿!你可能认为这样做效果太简陋了,应当要加入动画啥的。我已经考虑过,也加入了。只是因为这些增强效果不在本文范围内,所以我就不写了。

我的全部代码都放在 GitHub 里,欢迎来看看。

相关文章

网友评论

      本文标题:vue.js如何演示思维导图

      本文链接:https://www.haomeiwen.com/subject/clrdzdtx.html