开发绝大多数 web 前端应用的时候,我们关心的是文字/多媒体、区块排版、布局、样式和交互,关心的是如何给用户『阅读』体验更好的『网页』。 而开发可视化应用,我们关心的则是数据计算、不止是区块的各种图形的绘图渲染,当然也少不了交互,希望通过一张图让用户看清数据背后隐藏的信息。可视化的本质也就是把数据组织起来(比如从非结构化变成结构化)之后绘图呈现。
可视化开发中我们可能会相对少的用到 html 和 css,更多用到 canvas、webgl、svg 之类的图形 api。因为可能要处理视觉细节,很多时候即时用了主流的可视化图或图表库,我们依然需要接触底层渲染层 api 精细控制图形渲染细节。
不管可视化和一般 web 前端开发有多少不同,最终都还是通过浏览器渲染呈现的。浏览器通过自身的渲染引擎绘制图形,主要有两类方式:
可能提到可视化我们第一反应不会想到 html 和 css (实际中也极少用) ,但这个方式确实适合实现布局简单(区块式)的常规图表可视化。 这种方式主要靠 css 技巧,比如:柱状图可以通过 grid layout + linear-gradient 实现;饼图可以用 conic-gradient 圆锥渐变实现,折线区块图可以用 clip-path 拼多边形实现等等。
这里还提到了 svg,由于浏览器也支持直接内嵌 svg 标签,支持我们通过 dom api 操作 svg、css 处理 svg,同时提供了很多图形元素标签(html中需要 css 实现)。所以 svg 本质上是一种有特殊属性的 dom ,也可以实现可视化,本质上跟用 html 实现没有区别。
由于 svg 基于 xml 语法,我们可以比较方便的做视图和数据的解耦:
相比之下 css 至少需要理解一次比例转换:
优点
缺点
提到 canvas 我们一般是指代浏览器的 canvas2d 上下文,而准确的说浏览器的 canvas elements 提供了 2d、webgl 和 webgl2 三种我们常用的上下文 api (其实还有 bitmaprenderer, placeholder, 这里不展开,感兴趣的可以看 html spec canvas context mode)。
从 api 规范角度看,canvas api
规范有 canvas2d
规范和 webgl
规范两类,webgl
规范是 opengl es
(opengl es
是 opengl api
的子集,针对手机等终端设计)规范在 web 端的实现,webgl2
和 webgl
分别对应 opengl es 3
和 opengl es 2
。最新的还有 webgpu
,它是最新的 3d 图形api,相比 webgl 有很大升级和差异,不再基于 opengl 而是 vulkan/direct3d,这里不展开讲。
不同于 html/svg 的声明式语法,我们通过 canvas api 是以指令式的方式绘图。canvas 元素在浏览器上创建画布并提供上下文接口,我们调用 api 绘制点线面登记本图形、设置图形属性、颜色、形状变换,从而完成绘图。
另一点, canvas2d 和 webgl 的绘制最终也都是给到 gpu 渲染 (大部分现代浏览器都会 gpu 加速,dom
渲染最后也是给到 gpu 位图),他们相比 html/svg 方式最大的不同是不存在 dom renderObjectTree -> renderLayer
的流程。
不过 canvas2d 和 webgl 对 gpu 的控制有所不同,我们无法通过 canvas2d 直接操作 gpu,我们只能调用 canvas2d 简陋的api,浏览器在底层控制 gpu。webgl 预渲染到帧缓存后,再执行 webgl api (webglprogram)操作shader,因此我们可以在这里控制 gpu 渲染,在 shader 中做更精细化的操作或计算,性能远超 js。尤其是对于图形数量庞大的绘制,我们可以利用 webgl 实例化渲染并让 gpu 并行处理实现批量渲染,而 canvas2d 在浏览器底层并不会主动做这种优化。
canvas 优点
canvas 缺点
webgl 优点
webgl 缺点
还有点题外话,我们这里说的是 canvas elements 都是浏览器实现提供的,浏览器提供的东西最重要的就是通用性、跨平台、标准化。浏览器实现 canvas 至少要编译适配几个通用图形库,同时还需要再封装出一套 js api 给开发者用。
所以浏览器上 canvas 的绘制调用流程是:canvas js api -> js引擎 -> 浏览器接口逻辑 -> 图形库 -> gpu 绘制
。
比如 webkit 封装了 skia
图形库,chromium 会用 skia
的 software rasterizer
绘制成位图,作为纹理上传到 gpu,或由 skia
的 opengl 直接绘制纹理到 gpu (感兴趣可以看 GPU Accelerated Compositing in Chrome)。
但这一层层的封装避免不了性能消耗,尤其在 js 引擎这一换。而 native 可以直接调用opengl 或其他底层接口。 所以 浏览器 canvas 的渲染表现会比 native 差很多。当然 native 也不具备浏览器的通用开放跨平台性。
可以看到在浏览器中,无论用哪种方式绘图都依然有缺点。首要缺点就是,浏览器是通用平台,通用平台都只会提供基础 api,不会提供跟应用场景强相关的高级 api(所以前面说 canvas api 简陋),而开发者实际中需要的是高级 api,比如 drawGraph
、drawshape
之类的。这种高级 api 需要可视化引擎/库去实现。
因此最常见的是这种提供丰富绘制 api 的图表库,比如 echarts、g2;以及一些专业的图绘制,比如画地图地理的 mapbox、L7;还有一类无关应用场景、更灵活的绘制图形或物理模型,2d 的 spritejs、3d 的 threejs。让人不用通过 canvas2d/webgl 等底层 api 从基础图形画起。
前面提到可视化除了绘图之外的另一个关键是数据组织,所以还有一类专门用于数据处理、结构转换的框架,比如 d3.js。
事实上绝大多数可视化绘图(由于2d场景较多)都是基 canvas2d
(3d场景肯定是用 webgl)。
canvas2d
的两大缺点:
webgl
并行批量渲染优化;