1

离上次写文章已经挺久了,期间因为跟着项目一起换了组,一直非常忙。现在做的项目是公司内部全部组要用的 viewer 库. Viewer 需要的功能非常的多,其中的一个就是需要提供一些常用的绘图API功能, 比如用户鼠标移动画箭头,画圈圈,高光选中文本等等。

挑战

目前遇到的挑战就是在 canvas, svg, dom + css 之间如何选择的问题,canvas 绘图的方案已经有了成品,我们可以直接拿过来添加到现有的 Viewer 上面。但是基于以下考虑,主要用哪种技术迟迟不能下定论。

Canvas 纠结点:

1) 各部门合作问题,如果采用 canvas, 其他部门需要添加自定义图形操作的时候就需要他们了解 Canvas 开发 重绘到 Viewer 已有的 Canvas 上面。
2) Canvas 会失去很多 Element 原生的属性,比如文字选中,aria标签, 事件等。
3) Viewer 的文件可能会非常的大,并且可能会有成千上万个页面,每个页面管理自己的绘图。如果每个页面一个Canvas, 性能会非常差(采用了 View Virtualization 思想,只渲染视图需要的元素,这里是做了个极限假设)。如果所有的图形都绘制在一个Canvas上面,需要根据视图区域的页面属性重绘图形计算,开发成本会增加非常多。
4)Canvas 只能用于最底层,否则会覆盖其他元素(Viewer上面的层非常多,需要绘图层能管理事件,同时不丢失其他层的事件)。

于是老大就给几天时间,让我们尽情地先试着用其他的方式画一下各种图形,比较一下优劣(最后,我们应该会几样技术结合起来用)。其中一个案例就是高光选中文本,后端传会传一堆需要高光块儿大小位置,前端画出高光部分。 像这种非常多小面积的绘图,Canvas是最合适不过的了,不过这几天时间就是看看有没有还有什么其他的方案。大概就长这样,高光是无数个小块儿。
hightlight.jpg

寻找方案

Canvas 画高光的方案已经有了,我们首先想到的就是每一个块儿给个 span 渲染,但因为需要高光的块儿可能会非常多,并且可能会是动态的。哪么这就涉及到 DOM 的频繁操作,性能非常不好。后来采用createDocumentFragment,添加到DOM,性能明显提升。

我当时有个非常大胆的想法,要是只用一个span,然后所有的块儿都添加成背景可不可以实现(考虑到现在的css3已经有了多背景重叠技术)。找路子的时候千万次觉得不可能,结果实现之后再返回去看,其实原理非常的简单。全部采用css, 用linear-gradient画块儿(采用linear-gradient, 是因为它可以背景重叠,如果有更好的方法欢迎指教),background-position, background-size分别给每个块儿定位,结束!性能当然是惊人的好,因为根本没有涉及到元素的增加删除,只是css重绘(Viewer 有大量消耗性能的scroll监听,操作等情况下都能无缝操作,Canvas 有非常微小地快闪)。这一试,仿佛大概了新世界,很多大量,简单的图形绘制完全可以尝试纯css实现,上代码。

Angular template

<span class="layer-content" [style.background]="_background" [style.backgroundSize]="_backgroundSize" [style.backgroundPosition]="_backgroundPosition"></span>

Angular ts file

    this._subscription = this.highlights$.subscribe((highlights) => {
      let background = ``;
      let backgroundSize = ``;
      let backgroundPosition = ``;
      for (let i = 0; i < highlights.length; i++) {
        const { x, y, width, height } = highlights[i];
        // add connection comma when i is not the last one.
        const comma = (i < highlights.length - 1) ? ', ' : '';
        // 0px transparent to fill gradient syntax.
        background += `linear-gradient(CurrentColor 100%, transparent 0px)${comma}`;
        backgroundSize += `${width}px ${height}px${comma}`;
        backgroundPosition += `${x}px ${y}px${comma}`;
      }
      this._background = this.sanitizer.bypassSecurityTrustStyle(background);
      this._backgroundSize = backgroundSize;
      this._backgroundPosition = backgroundPosition;
    });

Css

span.layer-content {
  z-index: 0;
  display: inline-block;
  width: 100%;
  height: 100%;
  background-repeat: no-repeat !important;
}

期间看了些 Canvas, Svg 比较的文章,其中一篇觉得比较有意思的分享给大家。

https://www.yworks.com/blog/s...

如果大家有其他好的方案,欢迎指教

--- 完----


JUANA
205 声望777 粉丝

大龄小白白~ 依旧坚强地过好每一天~