浅谈 Canvas 渲染引擎设计( 五 )


import { Renderer as CanvaskitRenderer } from '@antv/g-canvaskit';const canvaskitRenderer = new CanvaskitRenderer();关于跨平台的架构这里不做讲解 , 主要是抹平不同平台的差异 , 这里主要讲解一下针对于服务端渲染的不同处理 。
主流的服务端渲染方式有两种 , 一种是用 node-canvas 来输出一张图片 , 在 echarts 等库中都有使用 , 缺陷在于文本排版不够准确 , 对于自适应浏览器窗口的情况无法处理 。因此它不适用于文档直出的场景 。
const { createCanvas, loadImage } = require('canvas')const canvas = createCanvas(200, 200)const ctx = canvas.getContext('2d')// Write "Awesome!"ctx.font = '30px Impact'ctx.rotate(0.1)ctx.fillText('Awesome!', 50, 100)// Draw line under textvar text = ctx.measureText('Awesome!')ctx.strokeStyle = 'rgba(0,0,0,0.5)'ctx.beginPath()ctx.lineTo(50, 102)ctx.lineTo(50 + text.width, 102)ctx.stroke()// Draw cat with lime helmetloadImage('examples/images/lime-cat.jpg').then((image) => {ctx.drawImage(image, 50, 0, 70, 70)console.log('<img src=https://www.isolves.com/it/cxkf/bk/2023-02-28/"' + canvas.toDataURL() + '" />')}) 另一种就是通过 SVG 来模拟 Canvas 的效果 , 输出 SVG DOM 字符串 。但它的实现会比较麻烦 , 也无法 100% 还原 Canvas 的效果 。
但很多 Canvas 渲染引擎本身也支持 SVG 渲染 , 即使不支持 , 也可以通过 canvas2svg 这个库来进行转换 。
var ctx = new C2S(500,500);//draw your canvas like you would normallyctx.fillStyle="red";ctx.fillRect(100,100,100,100);//serialize your SVGvar mySerializedSVG = ctx.getSerializedSvg(); //If you really need to you can access the shadow inline SVG created by calling:var svg = ctx.getSvg();对于更加通用的场景来说 , 在浏览器端使用 Canvas 渲染 , 服务端使用 SVG 渲染是更合理的形式 。
在新版 ECharts 里面 , 针对 SVG 服务端渲染的能力 , 还支持了 Virtual DOM 来代替 JSDOM , 最后转换成 DOM 字符串 。
在飞书文档中使用了一种完全独立于 node-canvas 和 SVG 的解决方式 , 非常值得我们借鉴 。
由于飞书多维表格底层统一了渲染引擎 , 所有绘制元素都是 Widget(对齐 Flutter) , 可以脱水转换成下面 FVG 格式 。
 

浅谈 Canvas 渲染引擎设计

文章插图
 
一般来说 , 文档业务首屏加载是下面这么几步:
获取首屏数据 -> 资源加载 -> 首屏数据反序列化 -> 初始化 Model 层 -> 计算排版数据 -> Canvas 渲染
在飞书文档里面直出渲染层 Widget 的数据结构 , 这个数据结构是最后提供给 Canvas 渲染的数据 , 也就是已经经过了计算排版数据阶段 。
当渲染层 JS 资源加载完成后 , 直接省略反序列化、初始化 Model、计算排版数据等阶段 , 将 FVG 转换成 Widget 进行 Canvas 渲染 , 这一步非常接近于 React 的 hydrate , 很巧妙 。

【浅谈 Canvas 渲染引擎设计】


推荐阅读