CSS、SVG、Canvas特殊字体的绘制导出_程序源
CSS、SVG、Canvas特殊字体的绘制导出
时间:2023-03-16 10:01:59
科技观察
最近在项目中需要绘制特殊字体并导出,如下:简单说明:所谓绘制就是直观看到(预览状态),而导出嘛,就是把看到的东西转成图片(或者Canvas)进行后续处理。这里总结了三种方法,分别是CSS、SVG和Canvas。让我们来看看它们各自的区别和优缺点。1.CSS绘图和导出首先来看CSS,这是最简单的绘图方式。假设HTML是这样的。前端侦探
添加样式。.text{显示:flex;宽度:200px;高度:200px;证明内容:居中;对齐项目:居中;背景色:丽贝卡紫色;颜色:#fff;字体大小:36px;Regular;}这里给出了一种特殊的字体MFMengYuan-Regular(梦元-Regular)。当然现在肯定是没有作用了,因为系统没有这样的字体。为了让这个特殊字体生效,需要通过@font-face手动定义。@font-face{font-family:"MFMengYuan-Regular";src:url("https://webfontsource.yuewen.com/api/v1/yfont/font.eot?base64=0&fnotallow=MFMengYuan-Regular&text=%E5%89%8D%E7%AB%AF%E4%BE%A6%E6%8E%A2");/*IE9*/src:local('?'),url("https://webfontsource.yuewen.com/api/v1/yfont/font.woff2?base64=0&fnotallow=MFMengYuan-Regular&text=%E5%89%8D%E7%AB%AF%E4%BE%A6%E6%8E%A2")format("woff2"),url("https://webfontsource.yuewen.com/api/v1/yfont/font.woff?base64=0&fnotallow=MFMengYuan-Regular&text=%E5%89%8D%E7%AB%AF%E4%BE%A6%E6%8E%A2")格式("woff"),url("https://webfontsource.yuewen.com/api/v1/yfont/font.ttf?base64=0&fnotallow=MFMengYuan-Regular&text=%E5%89%8D%E7%AB%AF%E4%BE%A6%E6%8E%A2");}这里是网上生成的一个字体,对于CSS来说也是小菜一碟,效果如下:IsitVeryeasy?CSS画图很容易,但是现在只是视觉上的,那么如何把这个样式转成图片导出呢?在这里,您需要使用SVG中的foreignObject[1]元素。通过这个元素,你可以将HTML嵌入到SVG中,例如:
前端侦探
一些截图工具库,比如html2canvas都是依赖foreignObject的特性,SVG本质上就是一张图片,然后可以将这张图片绘制到Canvas上进行进一步的图片合成和处理。总体思路如下:不过需要注意的是,SVG是一张独立的图片,它必须包含绘制内容的所有信息,比如你需要手动将样式style嵌入到div中,像这样(代码结构可能不太好看)。前端侦探接下来通过JS用foreignObject元素包裹起来,注意特殊字符的转义consthtmlSvg=`data:image/svg+xml,${dom.outerHTML}`.replace(/"/g,"'").replace(/%/g,"%25").replace(/#/g,"%23").replace(/{/g,"%7B").replace(/}/g,"%7D").replace(//g,"%3E");img.src=htmlSvg得到的SVG字符串是一张完整的图片。等等……图片出来了,但是字体好像不见了?为什么会这样?原因是以上字体使用的是在线字体。在线字体转换成字符后,就是普通字符了。不会提出要求,自然不会包含字体的真实信息。因此,要解决这个问题,必须提前转换字体。转换为本地base64格式,如下前端侦探所以它工作正常(SVG字符可能比较长)。也可以在Canvas上绘制此图片。constcontext=canvas.getContext('2d');context.drawImage(htmlSvg,0,0,宽度,高度);效果如下:另外,Canvas还可以将图片转为blob地址,相比完整的SVG地址而言,地址更加简洁。有时图片太大,将其赋值给图片src会导致浏览器卡顿。尝试使用blob方法。canvas.toBlob(function(blob){img.src=URL.createObjectURL(blob)})效果如下:完整的转换过程可以查看以下链接:CSS字体-代码掘金(juejin.cn)[2]CSSfont(codepen.io)[3]CSSfont(runjs.work)[4]2.SVG绘制与导出下面我们来看一下SVG的方法。与CSS相比,可能会稍微麻烦一些,主要是在文字排版方面,另外还需要注意Fontbase64的处理。<样式>@font-face{font-family:"MFMengYuan-Regular";src:local('?'),url(data:font/woff;charset=utf-8;base64,d09GRgABAAAAAAwcAA0AAAAAAEPgAAAAAAAAAAAAAAAAAAAAAAAAAAAA...==)格式('woff');}.text{背景颜色:rebeccapurple;}前端侦探这里需要注意SVG中的文本居中方式,使用dominant-baseline(基线对齐)和text-anchor(锚点对齐),如如下:将两者结合起来,然后配合x=50%和y=50%实现水平和垂直居中的效果,如下:既然已经是SVG了,导出一张图片或者画出来就更方便了Canvas画布,只需要对整个dom结构进行转义,不需要额外的封装。consthtmlSvg=`data:image/svg+xml,${dom.outerHTML}`.replace(/"/g,"'").replace(/%/g,"%25").replace(/#/g,"%23").replace(/{/g,"%7B").replace(//g,"%7D").replace(//g,"%3E");img.src=htmlSvg效果如下:绘制到Canvas也是同样的方法constcontext=canvas.getContext('2d');context.drawImage(htmlSvg,0,0,width,height);效果如下:完整的转换过程可以查看以下链接:SVG字体-代码掘金(juejin.cn)[5]SVG字体(codepen.io)[6]SVG字体(runjs.work)[7]3.Canvas的绘制和导出最后是Canvas方法。这里要画的东西很简单,就是一个矩形和一行文字,主要步骤如下:constcontext=canvas.getContext('2d');context.fillStyle='rebeccapurple'//填充颜色context.fillRect(0,0,width,height)//绘制矩形context.fillStyle='#fff'//填充颜色context.font=`36pxMFMengYuan-Regular`;//设置字体属性context.textAlign='center';//设置文本对齐上下文。textBaseline='middle'//设置基线对齐context.fillText('前侦探',width/2,height/2);//绘制文字效果如下:不出所料,没有绘制字体,因为系统没有这种字体,如何主动添加字体呢?这里有一个策略。Canvas读取页面上呈现的字体。也就是说,如果在页面上预先渲染好字体,那么在绘制的时候就可以直接绘制出来。如果字体是动态的,则可以动态创建。constfontStyle=`@font-face{font-family:"MFMengYuan-Regular";src:local('?'),url(data:font/woff;charset=utf-8;base64,d09GRgABAAAAAAwcAA0AAAAAEPgAAAAAAAAAAAA...==)格式('woff');}`conststyle=document.createElement('style')style.textContent=fontStyleddocument.head.appendChild(style)现在重新绘制,如下:可以看到一开始是没有字体的,刷新然后绘制新的字体。原因是之前的代码只是表示页面有这个字体,但是还没有渲染出来。通过Canvas绘制后,实际上是渲染了字体,所以字体要等到第二次才会生效。一旦知道了原因,解决方法就很简单了。如果不是实时绘制的,比如预览状态是通过CSS绘制的,那么Canvas绘制的时候(比如点击按钮生成预览图),字体当然已经渲染好了,自然也就有了不会有这样的问题。如果一定要实时绘制,可以采用逐帧对比的方法。一旦图像发生变化,则表示字体渲染成功。实现如下。这个方法参考了张新旭老师的这个文档:canvasAPI中文网-中文文档-CanvasRenderingContext2D.font[8]。//先画一个随机字体context.font=`36pxUNKNOW`;context.fillText('前端侦探',width/2,height/2);constdataDefault=context.getImageData(0,0,width/4,height/2).data;constdetect=function(){//然后绘制实际字体context.font=`36pxMFMengYuan-Regular`;context.fillText('前侦探',width/2,height/2);//如果前后数据一致,说明字体没有加载成功,继续检测vardataNow=context.getImageData(0,0,width/4,height/2).data;if([].slice.call(dataNow).join('')==[].slice.call(dataDefault).join('')){console.log('nochange,re-render')requestAnimationFrame(探测);}};以便实时绘制特殊字体。Canvas本身就是一张图片,可以直接转成图片,也可以导出,这里就不介绍了。完整的实现过程可以查看以下链接:Canvas字体-代码掘金(juejin.cn)[9]Canvas字体(codepen.io)[10]Canvas字体(runjs.work)[11]4.总结各有优缺点下面简单总结一下各实现的难易程度。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字体-代码掘金(juejin.cn):https://code.juejin.cn/pen/7170205919391776801。[3]CSS字体(codepen.io):https://codepen.io/xboxyan/pen/oNydKrv。[4]CSS字体(runjs.work):https://runjs.work/projects/62abc5942d9042b5。[5]SVG字体-代码掘金(juejin.cn):https://code.juejin.cn/pen/7170214063337635853。[6]SVG字体(codepen.io):https://codepen.io/xboxyan/pen/zYaaOvb。[7]SVG字体(runjs.work):https://runjs.work/projects/e2ff8774f0d0463b。[8]CanvasAPI中文网-中文文档-CanvasRenderingContext2D.font:https://www.canvasapi.cn/CanvasRenderingContext2D/font#&others。[9]Canvas字体-密码上掘金(juejin.cn):https://code.juejin.cn/pen/7170227469218218014。[10]Canvas字体(codepen.io):https://codepen.io/xboxyan/pen/ExRRYMV。[11]Canvas字体(runjs.work):https://runjs.work/projects/fc8d17a0c2494531。