原生js+css+html实现时序图

前言

前面我用React + AntDesign 实现了一个简单的时序图,可是后来有了更复杂的需求,而且要求同时展现2000个任务的展现,这就涉及到了性能问题,本人先使用React+antd+ts实现了一个基本知足下面需求的demo,可是react的渲染机制形成了较大的性能问题,利用chrome自带的Performance,测试发现demo的首次渲染高达10s以上,而且后续的操做也会使整个页面很是卡。时序图设计到的折叠和展开功能涉及到整个页面的重绘,通过思索后决定使用原生js+css去实现,由于原生js下性能是最优的。javascript

需求

什么是时序图

  1. 有一个以时间为单位的坐标轴。
  2. 以任务的耗时时长计算任务在页面上的显示长度。
  3. 用线条链接的方式表示任务之间的关系,可追踪子任务,父任务。例如:从何时建立,从何时开始。
  4. 每一个任务里附带有任务的信息。例如:任务的id,任务的名字,任务的状态。
  5. 用不一样颜色区分任务的运行状态。

功能点

下面先来讲说新版本的需求:css

  1. 左侧以树形结构展现任务,可折叠
  2. 右侧展现任务运行所耗时间的长度
  3. 须要用线条连接任务之间的关系
  4. 右侧可缩放查看详细的任务状态
  5. 缩放时图形保持以鼠标为中心向两端成必定比例放大,放大时里面的文字描述不受影响
  6. 图形缩放时表示任务耗时的时间以及坐标须要跟随图像放大的比例进行相应变化
  7. 鼠标在时序图上移动时出现一根线条提示当前的时间以及信息

效果图

图一:html

图二:前端

实现

如何实现鼠标滚轮缩放?

鼠标缩放产生时序图X轴的缩放。时序图的缩放,在这里提供三种思路:html5

  1. 作数据截取,按照必定的算法截取先后的数据,而后从新渲染整个页面
  2. 利用css3的scaleX对时序图的dom作缩放
  3. 实际改变时序图dom的width,里面的任务运行的长度,链接线条的长度,任务运行预计须要的时长都以百分比显示。

三种思路的优缺点:java

  1. 优势:不须要去操做dom的css属性,之间从新渲染,比较方便。缺点:对于使用dom重绘,耗费性能严重,大量任务渲染时性能很慢。
  2. 优势:只需改变dom的css,加载快,较流程。缺点:计算麻烦,使用过scaleX的小伙伴会发现当我X轴放大时垂直链接线会变宽,字体会横向拉伸,都须要去反向缩小。
  3. 优势:加载快,很流畅,一次计算好元素所占宽度的占百分比,后面的操做都不须要去计算。缺点:使用百分比计算会有必定偏差,放大到必定程度会看的出来。(综合考虑,本人采用的是第三种)
// 计算宽度百分比的函数
// endTime: 任务的结束时间
// startTime: 任务的开始时间
// maxTime: 全部任务结束时间最大的值
// minTime: 全部任务开始时间最小的值
// time: 全部任务开始时间与结束时间的排序 升序
// task_width: 任务的长度、水平链接线的长度、垂直链接线的left值
const widthFun = function (endTime, startTime, maxTime, minTime) {
    const task_width =
    (((Number (endTime) - Number (startTime)) /
    ((maxTime || time[time.length - 1]) - (minTime || time[0])) *
    (body_width - tree_box_dom.offsetWidth)) / dom.offsetWidth)*100;
    return task_width> 100 ? 100 : task_width;
  };
复制代码

如何在缩放时保持以鼠标为中心?

先放推理过程图:react

// 上图解释
// dom = 时序图的dom元素
// domL1, domeL2 = dom.scrollLeft;
// domeL1表示前一次的dom.scrollLeft;
// domeL2表示当前的dom.scrollLeft;
// scale 表示当前的放大的比例
// scale1 表示上一次的放大比例
// tree_dom.offsetWidth表示左侧树的宽度
// clientX1 表示上一次的鼠标位置距离时序图左侧的距离 = e.clientX - tree_dom.offsetWidth
// clientX2 表示当前鼠标位置距离时序图的距离

<!-- 当鼠标缩放时,获得公式 -->
domL2 = domeL1(scale/scale1) + clientX1(scale/scale1) - e.clientX + tree_dom.offsetWidth
// 公式讲解:
// 1. scale/scale1表示本次的缩放比例除以上一次的缩放比例,表示当前的缩放比例
// 左侧卷去的宽度在第二次缩放时也会跟着缩放,因此左侧的宽度须要乘以缩放比例
// 鼠标位置距离时序图左侧的宽度在缩放时也会跟着缩放,因此也要乘以缩放比例
// 最后面减去鼠标位置距离时序图左侧的实际距离就等于缩放时左侧卷去的长度

// 页面代码
time_box_parent.scrollLeft = (time_box_parent.scrollLeft + e.clientX - tree_box_dom.offsetWidth) * (scale_x / scale_x1) - e.clientX + tree_box_dom.offsetWidth;

复制代码

如何高效的绘制任务连线?

方案:css3

  1. 采用的时css3 + js + html5,用伪元素绘制。
  2. 用dom包裹直角图片,设置其位置及高度。
  3. 用标签绘制

优缺点:git

  1. 优势:不会增长多余的标签,对渲染有利。缺点:父任务产生了多个子任务,很差添加伪类及设置伪类的高度及宽度。
  2. 优势:方便,只要计算子任务距父任务的高度便可。缺点:任务过多时图片会很是多,很影响性能
  3. 优势:单独控制每一个元素的高度及位置,可控性高,可添加反馈色。缺点:添加了较多的元素,对渲染产生影响(本人使用的是第三种,这是一个笨方法,有更好方法的大佬,能够提供建议,多谢)

实现思路:github

用一个变量记录每一个任务的层级深度,层级深都以当前任务的父任务为起点,就是说是从哪一个任务产生的当前任务,同级的子任务进行累加操做。用累加的变量按照必定的比例获取垂直连线的高度以及水平连线的top值,水平连线的长度由任务的建立时间和开始时间决定。(使用上面的宽度百分比函数)

如何在缩放时调整横轴单位

这个比较简单,实现思路:

由于本demo的时间4刻度是个刻度,判断最小时间戳与最大时间戳之间的差除以4,是否还有一天的时间(60 * 60 * 24,换算成秒),从大到小的降序获取时间单位。

总结

使用原生js+html+css比使用框架渲染大量demo性能更好。完成了一样的效果与功能,使用chrome工具测试,渲染时间直用了1.3s左右,相较于以前的性能提升了10倍以上。固然框架有框架的优点,React中,render执行后得到的并非真正的DOM节点,而是一个虚拟的Virtual DOM (JavaScript对象),虚拟DOM具备batching(批处理)和高效的Diff算法,虚拟DOM与真实DOM造成对比,当虚拟DOM发生变化时,把变化的部分同步到真实的DOM上面去,造成局部更新。假如咱们2000个元素分红20个逻辑块,每一个逻辑块包含50个元素,这时若是咱们采用React的话就只须要更新对应的逻辑块,在这种状况下React的Virtual DOM的渲染效率就会更高。由于咱们的时序图是整个页面的刷新,大量DOM的重绘,因此用原生效率更高。最后在多说一句:框架要会用,原生是根本。

思路指导:yalishizhude

参考资料:

React虚拟DOM浅析

OpenTracing

原文连接:tech.gtxlab.com/timingDiagr…

做者信息:宁文飞,人和将来大数据前端工程师

相关文章
相关标签/搜索