呆呆带你手撸一个思维导图-基础篇

avatar
Web前端 @CVTE_希沃

希沃ENOW大前端

公司官网:CVTE(广州视源股份)

团队:CVTE旗下未来教育希沃软件平台中心enow团队

本文作者:

前言

你盼世界,我盼望你无bug。Hello 大家好,我是霖呆呆!

哈哈哈,好久没写文章了,不过今日再次提笔就像和一位很久没见面的好朋友约了顿饭,熟悉的感觉又回来了[窃喜~]。

希沃ENOW大前端也已经发了十几篇文章,一轮了,感谢大家的支持和喜欢啦[笔芯~]。

这周给大家带来的是一篇实际开发场景不多,但却比较有意思的文章——关于如何实现一个思维导图。

唔...由于是系列的文章,所以可要做好长期作战的准备了哟,整个系列我大体是将它分为了这么几个章节:

  • 基础篇:实现思维导图的一种方案
  • 实战篇:react-topic项目初步搭建
  • 实战篇:连线的具体实现
  • 实战篇:能动的导图

根据实际情况,后面可能会写更多吧。

对于基础篇,呆呆主要是会提供一些思维导图的实现方案(非架构方面),并介绍一些svg的相关知识,为后期手撸做好准备。

思维导图:"手撸对身体不好"

呆呆:"喂,110,这里有人酒后开车"

来看看,通过阅读这篇文章你可以学习到:

  • 明确目标
  • 节点的实现思路
  • svg及二次贝塞尔曲线
  • 连线的实现思路

1. 明确目标

对于思维导图,当前市场上已经存在很多了,也不乏有一些挺不错的产品,甚至还有专业做这个的团队。

XMind:

语雀:

processon:

在要开撸之前,我们得先明确自己想要做一个什么样的产品,以及做它的意义何在,最终实现出来的产品定位是怎样的。

那么呆呆先来说说自己的想法吧。其实写这个系列的想法很简单:巩固 + 分享,因为呆呆目前的工作有一部分是与之相关的,也是自己对其的一种巩固,也许自己在写文章的时候,能产生更多好的想法用在实际工作中;另外在工作之余分享一些已有的实现方案并可以与更多的小伙伴交流;最后,如果你认为你现在的工作有点无聊了,那么跟着呆呆来手撸一个思维导图它不香吗?[偷笑~]。

至于最终要做出一个什么东西,我们看到上面例举出的思维导图,都有很多的相似点:

  • 类型丰富,左右子树布局(我们称为脑图)、组织结构图、鱼骨图等。
  • 节点都可以进行拖拽移动改变层级
  • ...

那么我们也可以来实现一个类似于上面这些的一个思维导图,最终的效果类似于下面这样:

思维导图演示.gif

产品的定型为非商业(想做到商业也很有难度啊),自己练练手,希望在这个过程中,能让你学到一些奇怪的知识

2. 节点的实现思路

2.1 方案选型

一个思维导图的实现,主要的部分无外乎两点:

  • 节点(主题)
  • 连线

对于节点的实现,方案也有很多,无论是用普通的DOM元素,还是用svg,都可以。

例如,语雀中就是使用svgpath元素:

然后进入编辑态时,开启DOM元素的contenteditable属性:

这样的实现让我想到了bpmn.js,它也是完全由svg构成:

(嘻嘻,既然说到了这,哎,推一波推一波:《全网最详bpmn教材》)

而对于processon,好家伙,更加暴力...直接用的绝对定位:

呆呆这边也有一种推荐的实现方案:DOM元素 + flex布局。

2.2 DOM元素 + flex布局

选用DOM实现节点的原因:操作方便,API丰富,同时也易于扩展。

而对于它位置的实现,例如对于一个脑图来说,我们从整体上可以把它分为三个部分:

  • 左子树
  • 根节点
  • 右子树

这三个部分的排列,顺序上是比较固定的,那么我们就可以利用flex布局中的order属性来完成一个脑图的总体布局。

对于子树部分呢?我们也可以使用flex布局来实现,例如下图整个蓝色框的部分是右子树,绿色框部分为单个子树节点:

这样做的好处也是比较明显的,当节点的内容频繁变动的时候,不需要我们手动的去计算各个节点的位置,flex已经帮我们做好了这些事,实现动态的自适应布局。

我们可以简单的写个小demo来看看效果:

html代码:

<div class="mindMap">
  <div class="node rootNode">根节点</div>
  <div class="node subTree left">
    <div class="treeNode">子节点</div>
    <div class="treeNode">子节点</div>
    <div class="treeNode">子节点</div>
  </div>
  <div class="node subTree right">
    <div class="treeNode">子节点</div>
    <div class="treeNode">子节点</div>
    <div class="treeNode">子节点</div>
  </div>
</div>

css代码:

.mindMap {
  display: flex;
  align-items: center;
}
.node {
  width: 200px;
  background: rgb(192 214 255);
  text-align: center;
  color: #fff;
}
.rootNode {
  order: 2;
  background: #65bfff;
  height: 50px;
}
.left {
  order: 1;
}
.right {
  order: 3;
}
.subTree {
  display: flex;
  flex-direction: column;
  justify-content: center;
}
.treeNode {
  background-color: #ff6969;
}
.left .treeNode {
  margin-right: 56px;
}
.right .treeNode {
  margin-left: 56px;
}
.treeNode:not(:first-child) {
  margin-top: 24px;
}

最终的效果就是下面这样(丑是丑了点...咳咳凑合着看吧, 哈哈哈):

3. svg及二次贝塞尔曲线

3.1 svg简介

在继续下面的内容之前,呆呆得和大家科普一些svg的内容,它和我们后面的一些实现方案密切相关,所以我得保证你会一些最基本的知识不是。

svg为可缩放矢量图形,它使用XML格式来定义图像。我们可以像使用一个div标签一样直接在代码中使用:

<svg width="300" height="300">
  <line x1="0" y1="0" x2="200" y2="0" />
</svg>

而对于CSS属性,它有自己独有的三种属性:

  • fill:填充色
  • stroke:描边色
  • stroke-width:边框宽度

可以把svg标签想象成一个html标签,然后在这里面可以添加上path、line、rect等元素,来实现一个完整的矢量图。

我们常见的一些标签:

线,line标签:

效果:

矩形,rect标签:

效果:

路径,path标签:

效果:

椭圆,ellipse标签:

效果:

3.2 path元素

对于其它的一些元素呆呆这里就不展开了,可以查看《svg入门-如何手写》这篇文章。但是path元素我还是要单独说一下的,因为它是我们功能实现的"主力军"

path元素可用来创建线条、曲线、弧形等等。一个path元素形状的定义是依靠于属性 d, 属性d 的值是一个 “命令 + 参数”的序列。

啥意思呢?就像下面这段代码:

<svg>
  <path d="M10 10 L90 10"
    stroke="rgb(0,0,0)" stroke-width="5"
  ></path>
</svg>

它表示的是在页面中画一个路径,然后路径遵循:

  • 移动到坐标为 (10, 10) 的点;
  • 绘制一条线到坐标为 (90, 10) 的位置。

(M: Move to 移动到某个坐标 ;    L: Line to 在某两个坐标之间画一条线)

最终的效果:

是不是很好理解呢?

3.3 二次贝塞尔曲线Q

贝塞尔曲线大家都不陌生吧,在path元素里也是有的。而且它分为两种贝塞尔曲线:

  • 三次贝塞尔曲线C
  • 二次贝塞尔曲线Q

三次贝塞尔曲线C是由一个点和两个控制点决定的,相对复杂一些,我们着重说一下二次贝塞尔曲线Q

它是path元素中用来绘制平滑曲线的一种,一条二次贝塞尔曲线的定义需要三组参数:

  • 起点坐标
  • 控制点坐标
  • 终点坐标

控制点描述的是曲线起始点的斜率,曲线上各个点的斜率,是从起点斜率到终点斜率的渐变过程。

就像上面这张图,红色的点分别表示某个点和它的控制点。

下面这段代码:

<svg>
  <path
    path d="M10 80 Q 95 10 180 80"
    stroke="black" fill="transparent"
  ></path>
</svg>

它表示的是在页面中画一个路径,然后路径遵循:

  • 移动到坐标为 (10, 80) 的点;
  • 控制点的坐标为 (95, 10);
  • 终点的坐标为 (180, 80);
  • 根据控制点得到起点和终点的斜率并在它们两之间绘制一条曲线。

最终的效果为:

4. 连线的实现思路

上面介绍了这么多svg相关的知识,不知道有没有给你实现导图中的连线提供一些思路呢?

哈哈哈,也许我们可以把整个思维导图看成是一整块svg矢量图,然后在特定的位置用path去画上线,就实现了思维导图中的枝干。

这个特定的位置也比较好理解了,因为是枝干,那么肯定是在某两个节点之间,例如根节点和它的右侧一级子节点,我们可以取根节点最右侧的中点,和一级子节点最左侧的中点。

如果对于下面这种直线枝干的话,只知道这两个点的坐标确实是可以满足的(图中的蓝色点):

但如果是对于这样的曲线路径呢?

它就可以用我们上面提到的二次贝塞尔曲线Q来实现,此时根节点上的某个坐标就可以作为曲线的起点,分支主题上的某个坐标就可以作为曲线的终点。但我们清楚,只知道起点和终点坐标还是不够的,还需要有一个控制点,才能很好的实现一个曲线。

上面的截图取的是XMind上的思维导图实现,也许不太能看出线与节点之间的关系,让我们把它的枝干移动一下,放到中心主题的最右侧:

现在,就比较直观了(假设我们再给这些点一个坐标):

  • 起点:(x1, y1)     => (100, 60)
  • 控制点:(x1, y2)  => (100, 10)
  • 终点:(x2, y2)     => (150, 10)

(这里的坐标,是以思维导图整体最左上角为坐标轴的起点)

那么实现这么一条曲线就只需要下面这行代码:

<svg>
  <path
    d="M100 60 Q 100 10 150 10"
    fill="transparent"
    stroke="rgb(102, 102, 102)"
    stroke-width="4"
  ></path>
</svg>

咦~是不是感觉就有点内味了[偷笑~]。

我们就只需要再去实现一个传入两个DOM的信息,然后计算得出起点、终点、以及控制点的方法就能简单的实现连线了。

参考文章

知识无价,支持原创。

参考文章:

后语

这篇文章主要就介绍到了这里,全文看下来要手撸一个思维导图似乎很简单。那是因为我们还有很多问题没有考虑进去,例如怎么实现不同种类的思维导图,节点的框选,节点的拖拽移动等等。

就和前言中介绍的一样,这篇主要是提供一些思维导图的实现方案(非架构方面),并介绍一些svg的相关知识。后面呆呆将会构建一个项目,通过实战来和大家一起手撸它,所以请给个赞再点个关注吧,不然你可能就会错过我了哟...谢谢。