最近在项目中需要对特殊字体进行绘制与导出,如下:
简单解释一下:所谓绘制,就是视觉上可以看到就行(预览状态),导出呢,就是将看到的转换成图片(或者Canvas),以便于后续处理。
这里总结了 3 种方式,分别是 CSS 、 SVG、Canvas,来看看各自有什么差异和优缺点吧
一、CSS 的绘制与导出
首先来看 CSS ,这是最简单的绘制方式了。
假设 HTML是这样的。
加点样式。
这里给了一个特殊的字体MFMengYuan-Regular(造字工坊梦缘体),当然现在肯定是没有效果,因为系统并没有这样的字体。
为了使这个特殊字体生效,需要手动通过@font-face去定义。
这里引用的是一个在线生成的字体,对于 CSS 来说也是小菜一碟,效果如下:
是不是非常轻松?
CSS 绘制非常容易,但现在仅仅是视觉上的,那如何将这个样式转换成图片导出呢?
在这里,需要借助 SVG 中的foreignObject[1]元素,通过这个元素,可以将 HTML嵌入到SVG中,例如:
一些截图工具库,比如 html2canvas都依赖 foreignObject 这个特性。
而SVG本质上就是图片,然后就可以将这个图片绘制到 Canvas 上,进一步进行图片合成和处理了,整体思路如下:
不过需要注意的是,SVG是一个独立的图片,必须包含绘制内容的全部信息,比如这里需要手动将style样式内嵌到div中,就像这样(代码结构可能不是很好看)。
接下来通过JS将其包裹上foreignObject元素,注意一下特殊字符的转义。
这样得到的一个
SVG
字符串就是一个完整的图片了。
等等...图片是出来了,不过字体好像丢失了?
为什么会这样呢?原因在于,上面字体使用的是在线字体,
在线字体在转成字符后就是普通的字符了,不会发出请求
,自然也不会包含字体的真实信息了,所以要解决这个问题就必须提前将字体转成本地
base64
格式,如下
这样就正常了(SVG字符可能会比较长)。
同样也能将这个图片绘制到Canvas上。
效果如下:
除此之外,通过Canvas还能将图片转成blob地址,相比完整 SVG地址而言,地址更加简洁,有时候图片过大,在赋值给图片src会造成浏览器卡顿,尽量用blob方式。
效果如下:
完整转换过程可以查看以下链接:
- CSS font - 码上掘金 (juejin.cn) [2]
- CSS font (codepen.io) [3]
- CSS font (runjs.work) [4]
二、SVG 的绘制与导出
下面来看SVG方式,相比CSS而言,可能稍微麻烦一点,主要是文本排版方面,同样需要注意字体base64处理。
这里需要注意一下 SVG 中的文本居中方式,用到了dominant-baseline(基线对齐)和text-anchor(锚点对齐),如下:
两者结合,再配合x=50%和y=50%就实现了水平垂直居中效果了,如下:
由于已经是SVG了,所以导出图片或者绘制到Canvas画布上就更方便,只需要将整个 dom 结构转义一下就可以了,无需额外包裹。
效果如下:
绘制到Canvas上也是同样的方式。
效果如下:
完整转换过程可以查看以下链接:
- SVG font - 码上掘金 (juejin.cn) [5]
- SVG font (codepen.io) [6]
- SVG font (runjs.work) [7]
三、Canvas 的绘制与导出
最后是 Canvas方式。
这里要绘制的很简单,就是一个矩形和一行文字,主要步骤如下:
效果如下:
不出意料,字体果然没有绘制,因为系统并没有这种字体,那如何主动添加字体呢?
这里有一个策略,Canvas读取的是页面上已经渲染过的字体,也就是说页面上如果提前渲染过该字体,那么在绘制的时候就可以直接绘制出来,如果字体是动态的,可以通过动态创建。
现在重新绘制,如下:
可以看到,起初是没有字体的,刷新后才绘制新的字体。
原因是,前面这段代码仅仅是表示页面有这个字体,但是并没有渲染过,通过Canvas绘制后,这个字体才真正被渲染,所以到了第二次字体才生效。
知道原因后,解决就很简单了。如果不是实时绘制,比如说预览状态通过 CSS 绘制,那么等到 Canvas 绘制的时候(比如通过按钮点击生成预览图),字体当然已经渲染过,自然也不会有这个问题。如果一定要实时绘制,可以采用逐帧比对的方式,一旦图像发生变化,就表示字体渲染成功,实现如下
该方法参考自张鑫旭老师这篇文档: canvas API中文网 - 中文文档 - CanvasRenderingContext2D.font [8]。
这样就可以实时绘制特殊字体了。
Canvas本身就是图片了,直接可以转换成图片或者导出,这里就不多介绍了。
完整实现过程可以查看以下链接:
- Canvas font - 码上掘金 (juejin.cn) [9]
- Canvas font (codepen.io) [10]
- Canvas font (runjs.work) [11]
四、总结一下各自优缺点
下面简单整理了一下各自实现的难易程度。
CSS绘制最简单,尤其是在文本排版方面,要远远领先其他两种方式。
SVG绘制相对比较简单,在矢量图形处理,比如描边特效要比 CSS 更有优势,这两种方式导出的难点在于一些外链资源的额外处理。
而Canvas绘制稍微复杂一些,在特殊字体需要逐帧去检测是否渲染,优点是绘制出来就是图片,无需额外导出。
|
绘制 |
导出 |
CSS |
⭐️⭐️⭐️(简单) |
⭐️⭐️(一般) |
SVG |
⭐️⭐️(一般) |
⭐️⭐️(一般) |
Canvas |
⭐️(复杂) |
⭐️⭐️⭐️⭐️(超级简单) |
关于 CSS 和 SVG 的选择可以看实际文本排版需求,比如文本需要换行,字号大小也不一致,像这种情况 CSS 就比较有优势了,无需去精确计算文本坐标。
另外,在实际工作中,根据需求可能需要多种方式结合使用,也就是预览状态和导出状态分别用不同的方式实现,比如图片混合,在预览状态完全可以通过 CSS 实现,在导出时才通过 Canvas 去绘制合成。
参考资料
[1] foreignObject: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/foreignObject。
[2] CSS font - 码上掘金 (juejin.cn): https://code.juejin.cn/pen/7170205919391776801。
[3] CSS font (codepen.io): https://codepen.io/xboxyan/pen/oNydKrv。
[4] CSS font (runjs.work): https://runjs.work/projects/62abc5942d9042b5。
[5] SVG font - 码上掘金 (juejin.cn): https://code.juejin.cn/pen/7170214063337635853。
[6] SVG font (codepen.io): https://codepen.io/xboxyan/pen/zYaaOvb。
[7] SVG font (runjs.work): https://runjs.work/projects/e2ff8774f0d0463b。
[8] canvas API中文网 - 中文文档 - CanvasRenderingContext2D.font: https://www.canvasapi.cn/CanvasRenderingContext2D/font#&others。
[9] Canvas font - 码上掘金 (juejin.cn): https://code.juejin.cn/pen/7170227469218218014。
[10] Canvas font (codepen.io): https://codepen.io/xboxyan/pen/ExRRYMV。
[11] Canvas font (runjs.work): https://runjs.work/projects/fc8d17a0c2494531。