Web 端图形渲染方案这么多,到底该选哪一种?

我西瓜。

今天过一下 Web 图形渲染的各种方案。

渲染方案

Web 端的图形渲染的方案有 4 种,分别为:

  • HTML + SVG
  • Canvas 2D
  • WebGL
  • WebGPU

HTML + SVG

HTML + SVG 方案的优势是相对上手简单

作为 Web 前端开发,对 HTML 本身就比较熟悉,只需要另外学习一些 SVG 特有的描述图形的特别元素(如 rectpath)就可以上手进行开发了。

SVG 也是 XML 的一种,所以我们能拿到一棵 DOM 文档树,也能使用 DOM 元素的各种鼠标事件,基于它们实现移动悬停各种效果。

此外,我们还能用浏览器的开发者工具查看 DOM 树进行调试,所以这种方案也易于调试

但是基于 DOM 的鼠标事件在画布缩小的场景中不好点中,比如 10px 的线,画布缩小后可能变成 0.5px,很难选中。对此可能需要自己额外进行图形碰撞检测。

Canvas 2D

然后是 Canvas 2D。

我们可以在 HTML 使用 画布上元素,然后通过一些 Canvas API 在这个元素执行一些绘制命令,绘制各种几何图形,实现你想要的效果。

const canvas = document.querySelector('canvas');
// 获取 canvas 2d 上下文
const ctx = canvas.getContext('2d');

ctx.strokeRect(10.5, 10.5, 50, 50);

相比 HTML + SVG,Canvas 2D 更加底层,没有图形树,它更像是给你提供一支画笔,你自己考虑在画布上绘制什么东西。

绘制的东西 Canvas 2D 并不记录成图形树,你无法 “选中某个图形”,这些全都需要你自己去实现,包括图形对象设计、图形渲染、图形碰撞与拾取、鼠标事件封装等。

但 Canvas 2D 有一个很重要的优势,性能更优

此外我们也可以使用各种优化策略做性能上的优化(比如剔除、脏矩形渲染、缓存),尤其是有大量图形元素要渲染的场景。

Canvas 2D 的缺点是比较原始,很多东西都要自己处理,但相比 SVG 可以做更多的性能优化。

WebGL

再看看 WebGL。

WebGL 简单来说是 OpenGL 的方言,可以运行在浏览器上,目的是在网页上渲染三维图形。

渲染二维图形自然也完全没有问题,毕竟本来三维也要投影到二维渲染到屏幕上的。

WebGL 同样需要用到 画布元素,但是相比 Canvas 2D,它更底层一些,需要编写着色器,能够直接控制 GPU 通过并行计算实现渲染加速,性能更更一步提升。

// 用 WebGL 绘制一个三角形
const canvas = document.querySelector("canvas");
const gl = canvas.getContext("webgl");

const vertexShaderSrc = `
attribute vec4 a_Position;
void main() {
 gl_Position = a_Position;
}
`;

// ... 此处略去 50 多行
gl.drawArrays(gl.TRIANGLES, 0, 3);

用 WebGL 绘制三角形的完整 demo

https://codesandbox.io/s/gbh1xf?file=/index.js

但 WebGL 的问题在于它过于底层,开发和维护门槛极高,你需要理解着色器、缓冲区、纹理等各种知识点,但对于需要将性能压榨到极致的图形编辑器,WebGL 还是很有必要的。

Canvas 2D 如果是战国时代,那 WebGL 就是石器时代了。

WebGPU

最后是比较新的 WebGPU。

WebGPU 是一个更现代的 Web 上的图形渲染标准。WebGPU 也是绘制在 画布上。

WebGPU 的出现是为了取代 WebGL 的。WebGL 发布已经很长时间了,API 实在有些过时,无法利用好现代 GPU 的一些高级特性,本身的 API 设计也较难使用。

相比 WebGL,WebGPU 有更好的性能表现,API 更底层更灵活,并支持更高级的现代特性,比如计算着色器。

学习门槛和 WebGL 一样,相关的知识点还是需要的。

WebGPU 目前还比较年轻,并没有被所有主流浏览器完全支持,也有一些 BUG。

// 用 WebGPU 绘制一个矩形(浏览器不支持会报错)

const render = async () => {
  const canvas = document.querySelector('canvas')
  const ctx = canvas.getContext('webgpu');

  // ... 此处省略 80行代码
  pass.setPipeline(pipeline);
  pass.setVertexBuffer(0, vertexBuffer);
  pass.draw(vertices.length / 2);
  pass.end();
  const commandBuffer = encoder.finish();
  device.queue.submit([commandBuffer]);
};

render();

用 WebGPU 绘制三角形的完整 Demo

https://codesandbox.io/s/lg4w27?file=/src/index.mjs

不过鉴于其优异的性能优势,如果高性能是硬性规定,我们其实可以使用 “向后回退” 的策略。

即如果浏览器支持使用 WebGPU,优先使用 WebGPU 做渲染,如果浏览器不支持,则回退为 WebGL。这也是一些渲染库目前正在做的事情,比如 Pixi.js。

我对 WebGPU 的评价是:未来可期。

WASM

WASM(WebAssembly)本身不是一种渲染技术,但它能够在 CPU 上提高运行性能。

WASM 一种中间编译的字节码,可以在浏览器上运行非 JavaScript 语言,只要它能被编译成 WASM。

它的一个优点是效率更高,相比 JavaScript 能够以接近原生的性能运行,但问题是你需要用 C/C++、Rust 这种无 GC 的语言编写才有明显的性能优势。

且浏览器因为 V8 引擎的 JIT(即时编译)机制的原因,外加 JavaScript 和 WASM 之间的通信成本,WASM 相比 JavaScript 提升并不够明显,只有在有限的场景上能发挥较大优势。

WASM 是一种可选项,但对开发者的要求更多。可以用 C++ 编写 OpenGL,然后编译为 WASM,浏览器会解析为 WebGL 运行。此外 C++ 也有一些很强大的渲染库可以用,比如 Skia。

《WASM + OpenGL + C++ 入门:绘制三角形》

WASM 是构建高性能 Web 工具应用绕不开的技术,Figma 使用的便是 WASM。

图形库选型

前面提到了,和 HTML + SVG 不一样,Canvas 2D、WebGL、WebGPU 这些是直接提交绘制指令,在 画布元素上直接渲染图形,我们是需要自己手动维护一棵图形树的。

垂涎后者的更优秀的性能,于是开发者们基于它们的 API,做了一层封装,针对不同图形设计不同的数据结构,并实现对应的渲染方法,最后基于图形对象维护一棵图形树,且实现图形拾取并提供鼠标事件系统。

并提供类似 DOM 节点操作相关的 API,最后便得到一个图形库。

"We can solve any problem by introducing an extra level of indirection."

通过渲染库,我们可以像操作 DOM 树一样,进行添加图形、修改图形属性等各种操作。

自己实现一套渲染库工作量很大,通常我们会使用一些开源的优秀图形库。

这里给大家简单介绍一些比较流行的图形库,可以根据需要选择。

fabric.js

首先奔赴战场的是,fabric.js!

fabric.js 是基于 Canvas 2D 的图形库,除了基础的图形渲染,它的最大卖点在于封装好的部分编辑器功能,以及对导入导出 SVG 的支持。

我们在网上能看到不少开发图形编辑器的文章,或是一些图片编辑器,大部分都是用的 fabric.js。

fabric.js 在支持渲染图形的同时,还帮你实现了图形编辑器需要开发的一些业务功能。比如画笔工具、图形的拖拽移动、通过控制点旋转缩放选中的图形。

因此,我们可以用它快速实现一个图形编辑器原型。简单的 Demo 项目 fabric.js 还是挺合适的。

fabric.js 非常流行,所以文档和教程都比较丰富。

Konva.js

Konva.js 也是一款基于 Canvas 2D 的图形渲染库。

性能上做了优化,性能相对不错,且兼容移动端,文档比较丰富,相比 fabric.js 有良好的 TypeScript 类型支持,是使用的比较多的图形库

paper.js

paper.js 的 slogan 为 “矢量图形脚本的瑞士军刀”。

作为一款基于 Canvas 2D 的图形库,它的图形渲染特性是是足够的。但此外还提供了一些丰富的图形算法,比如路径做简化光滑化、路径的布尔运算。

如果你要做 贝塞尔曲线编辑功能,paper.js 丰富的 API 可能挺合适的。

不过 paper.js 目前已经不再维护添加新特性了,最后一个版本 v0.12.15 已经是 2021 年的时候发布的了。

Pixi.js

Pixi.js 是一款基于 WebGL 的渲染器。

最新的 v8 版本还加入了 WebGPU 渲染器,但还不稳定。

得益于 WebGL 带来的并发计算优势,Pixi.js 属于开源 2D 图形库中性能最强的一档。

Pixi.js 针对高性能做了大量的工作,牺牲了一些渲染效果,在一些场景下,渲染效果不是很精细。相对于其它基于 Canvas 2D 的图形库来说,Pixi.js 提供更多比较底层的能力。

如果你有更底层能力的要求,Pixi.js 是不错的选择,当然代价是花费更多的时间和精力,可以考虑找找有没有相关插件。

SVG.js

SVG.js 是一款基于 SVG 的图形库。

你可以理解为用于 SVG 的 jQuery,没什么特别好说的,就是把创建更新 SVG 图形元素的 API 封装了一层,提高了易用性。

如果你要使用 SVG 来开发,这个库就很适合。

LeaferJS

LeaferJS 是国人开发的一款基于 Canvas 2D 的图形库。

功能类似 fabricjs,有渲染图形的部分(单独放在一个包 leader-draw 里)以及编辑器的部分业务功能。

做了很多的性能优化工作,性能应该是挺不错的。目前作者全职维护 LeaferJS,所以不用担心 bug 不会被修复的问题。

相比老旧的 fabricjs,已经有人开始尝试替换为 Leafer 了。

Skia/CanvasKit

Skia 是一款基于 C++ 的开源的 2D 图形库。

Skia 正是 Chrome、Flutter 等项目底层的渲染引擎,前面我们提到的 Canvas 2D,在基于 chromium 内核的浏览器下,其底层就是 Skia。

因为 API 相比 Canvas 2D 更丰富,比如支持布尔运算、混合效果、高级文本特性,所以是有很多功能复杂的图形编辑器选择基于 Skia 用 C++ 进行开发的原因,然后编译为 WASM 让浏览器运行。

不过让 Web 前端开发去写 C++ 实在跨度过大,skia 也提供了 WASM 版本:CanvasKit。

CanvasKit 是有更多功能的 Canvas 2D,让我们可以用 JavaScript 编写业务逻辑。

Rough.js

Rough.js 是一款手绘风的轻量的图形库,支持 Canvas 2D 和 SVG 两种渲染方式。

该库的核心功能就是手绘风格,比如知名的手绘风白板工具 Excalidraw 便是使用了该库。

结尾

我是前端西瓜哥,关注我,学习更多前端图形知识。

原文链接:,转发请注明来源!