欢送关注我的公众号:前端侦探
最近在我的项目中须要对非凡字体进行绘制与导出,如下
简略解释一下:所谓绘制,就是视觉上能够看到就行(预览状态),导出呢,就是将看到的转换成图片(或者 Canvas),以便于后续解决。
这里总结了 3 种形式,别离是 CSS、SVG、Canvas,来看看各自有什么差别和优缺点吧
一、CSS 的绘制与导出
首先来看 CSS,这是最简略的绘制形式了。
假如 HTML
是这样的
<div class="text"> 前端侦探 </div>
加点款式
.text{
display: flex;
width: 200px;
height: 200px;
justify-content: center;
align-items: center;
background-color: rebeccapurple;
color: #fff;
font-size: 36px;
font-family: MFMengYuan-Regular;
}
这里给了一个非凡的字体MFMengYuan-Regular
(造字工坊梦缘体),当然当初必定是没有成果,因为零碎并没有这样的字体
为了使这个非凡字体失效,须要手动通过 @font-face
去定义
@font-face {
font-family: "MFMengYuan-Regular";
src: url("https://webfontsource.yuewen.com/api/v1/yfont/font.eot?base64=0&font=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&font=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&font=MFMengYuan-Regular&text=%E5%89%8D%E7%AB%AF%E4%BE%A6%E6%8E%A2") format("woff"),
url("https://webfontsource.yuewen.com/api/v1/yfont/font.ttf?base64=0&font=MFMengYuan-Regular&text=%E5%89%8D%E7%AB%AF%E4%BE%A6%E6%8E%A2");
}
这里援用的是一个在线生成的字体,对于 CSS 来说也是小菜一碟,成果如下
是不是十分轻松?
CSS 绘制非常容易,但当初仅仅是视觉上的,那如何将这个款式转换成图片导出呢?
在这里,须要借助 SVG
中的 foreignObject 元素,通过这个元素,能够将 HTML
嵌入到 SVG
中,例如
<svg xmlns="http://www.w3.org/2000/svg">
<foreignObject width="200" height="200">
<body xmlns="http://www.w3.org/1999/xhtml">
<div> 前端侦探 </div>
</body>
</foreignObject>
</svg>
一些截图工具库,比方 html2canvas 都依赖 foreignObject 这个个性
而 SVG
实质上就是图片,而后就能够将这个图片绘制到 Canvas
上,进一步进行图片合成和解决了,整体思路如下:
不过须要留神的是,SVG
是一个独立的图片,必须蕴含绘制内容的全副信息 ,比方这里须要手动将style
款式内嵌到 div
中,就像这样(代码构造可能不是很难看)
<div class="text">
<style>
@font-face {
font-family: "MFMengYuan-Regular";
src: url("https://webfontsource.yuewen.com/api/v1/yfont/font.eot?base64=0&font=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&font=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&font=MFMengYuan-Regular&text=%E5%89%8D%E7%AB%AF%E4%BE%A6%E6%8E%A2") format("woff"),
url("https://webfontsource.yuewen.com/api/v1/yfont/font.ttf?base64=0&font=MFMengYuan-Regular&text=%E5%89%8D%E7%AB%AF%E4%BE%A6%E6%8E%A2");
}
.text{
display: flex;
width: 200px;
height: 200px;
justify-content: center;
align-items: center;
background-color: rebeccapurple;
color: #fff;
font-size: 36px;
font-family: MFMengYuan-Regular;
}
</style>
前端侦探
</div>
接下来通过 JS
将其包裹上 foreignObject
元素,留神一下特殊字符的本义
const htmlSvg = `data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="${width/pixelRatio}" height="${height/pixelRatio}">
<foreignObject x="0" y="0" width="100%" height="100%">
<body xmlns="http://www.w3.org/1999/xhtml" style="height:100%;margin:0">
${dom.outerHTML}
</body>
</foreignObject></svg>`.replace(/"/g,"'").replace(/%/g,"%25").replace(/#/g,"%23").replace(/{/g,"%7B").replace(/}/g,"%7D").replace(/</g,"%3C").replace(/>/g,"%3E");
img.src = htmlSvg
这样失去的一个 SVG
字符串就是一个残缺的图片了
等等 … 图片是进去了,不过字体如同失落了?🤔
为什么会这样呢?起因在于,下面字体应用的是在线字体,在线字体在转成字符后就是一般的字符了,不会发出请求 ,天然也不会蕴含字体的实在信息了,所以要解决这个问题就必须提前将字体转老本地base64
格局,如下
<div class="text">
<style>
@font-face {
font-family: "MFMengYuan-Regular";
src: local('☺'),
url(data:font/woff;charset=utf-8;base64,d09GRgABAAAAAAwcAA0AAAAAEPgAAAAAAAAAAAAAAAAAA...==) format('woff');
}
.text{
display: flex;
width: 200px;
height: 200px;
justify-content: center;
align-items: center;
background-color: rebeccapurple;
color: #fff;
font-size: 36px;
font-family: MFMengYuan-Regular;
}
</style>
前端侦探
</div>
这样就失常了(SVG
字符可能会比拟长)~
同样也能将这个图片绘制到 Canvas
上
const context = canvas.getContext('2d');
context.drawImage(htmlSvg, 0, 0, width, height);
成果如下
除此之外,通过 Canvas
还能将图片转成 blob
地址,相比残缺 SVG
地址而言,地址更加简洁,有时候图片过大,在赋值给图片 src
会造成浏览器卡顿,尽量用 blob
形式
canvas.toBlob(function(blob){img.src = URL.createObjectURL(blob)
})
成果如下
残缺转换过程能够查看以下链接:
- CSS font – 码上掘金 (juejin.cn)
- CSS font (codepen.io)
- CSS font (runjs.work)
二、SVG 的绘制与导出
上面来看 SVG
形式,相比 CSS
而言,可能略微麻烦一点,次要是文本排版方面,同样须要留神字体 base64
解决
<svg id="svg" class="text" xmlns='http://www.w3.org/2000/svg' viewBox="0 0 200 200" width="200" height="200">
<style>
@font-face {
font-family: "MFMengYuan-Regular";
src: local('☺'),
url(data:font/woff;charset=utf-8;base64,d09GRgABAAAAAAwcAA0AAAAAEPgAAAAAAAAAAAAAAAAAAAAAAAAAAAA...==) format('woff');
}
.text{background-color: rebeccapurple;}
</style>
<text x="50%" y="50%" text-anchor="middle" dominant-baseline="middle" fill="#fff" font-size="36" font-family="MFMengYuan-Regular"> 前端侦探 </text>
</svg>
这里须要留神一下 SVG
中的文本居中形式,用到了dominant-baseline
(基线对齐)和text-anchor
(锚点对齐),如下
两者联合,再配合 x=50%
和y=50%
就实现了程度垂直居中成果了,如下
因为曾经是 SVG
了,所以导出图片或者绘制到 Canvas
画布上就更不便,只须要将整个 dom 构造本义一下就能够了,无需额定包裹
const htmlSvg = `data:image/svg+xml,${dom.outerHTML}`.replace(/"/g,"'").replace(/%/g,"%25").replace(/#/g,"%23").replace(/{/g,"%7B").replace(/}/g,"%7D").replace(/</g,"%3C").replace(/>/g,"%3E");
img.src = htmlSvg
成果如下
绘制到 Canvas
上也是同样的形式
const context = canvas.getContext('2d');
context.drawImage(htmlSvg, 0, 0, width, height);
成果如下
残缺转换过程能够查看以下链接:
- SVG font (codepen.io)
- SVG font (runjs.work)
三、Canvas 的绘制与导出
最初是 Canvas
形式。
这里要绘制的很简略,就是一个矩形和一行文字,次要步骤如下
const context = canvas.getContext('2d');
context.fillStyle = 'rebeccapurple'// 填充色彩
context.fillRect(0,0,width,height) // 绘制矩形
context.fillStyle = '#fff' // 填充色彩
context.font = `36px MFMengYuan-Regular`; // 设置字体属性
context.textAlign = 'center'; // 设置文本对齐
context.textBaseline = 'middle' // 设置基线对齐
context.fillText('前端侦探', width/2, height/2); // 绘制文本
成果如下
不出预料,字体果然没有绘制,因为零碎并没有这种字体,那如何被动增加字体呢?
这里有一个策略,Canvas
读取的是页面上曾经渲染过的字体,也就是说页面上如果提前渲染过该字体,那么在绘制的时候就能够间接绘制进去,如果字体是动静的,能够通过动态创建
const fontStyle = `
@font-face {
font-family: "MFMengYuan-Regular";
src: local('☺'),
url(data:font/woff;charset=utf-8;base64,d09GRgABAAAAAAwcAA0AAAAAEPgAAAAAAAAAAA...==) format('woff');
}
`
const style = document.createElement('style')
style.textContent = fontStyle
document.head.appendChild(style)
当初从新绘制,如下
能够看到,起初是没有字体的,刷新后才绘制新的字体。
起因是,后面这段代码仅仅是示意页面有这个字体,然而并没有渲染过,通过 Canvas
绘制后,这个字体才真正被渲染,所以到了第二次字体才失效。
晓得起因后,解决就很简略了。如果不是实时绘制,比如说预览状态通过 CSS 绘制,那么等到 Canvas 绘制的时候(比方通过按钮点击生成预览图),字体当然曾经渲染过,天然也不会有这个问题。如果肯定要实时绘制,能够采纳逐帧比对的形式,一旦图像发生变化,就示意字体渲染胜利,实现如下
该办法参考自张鑫旭老师这篇文档:canvas API 中文网 – 中文文档 – CanvasRenderingContext2D.font
// 先轻易绘制一个字体
context.font = `36px UNKNOW`;
context.fillText('前端侦探', width/2, height/2);
const dataDefault = context.getImageData(0, 0, width/4, height/2).data;
const detect = function () {
// 而后绘制理论字体
context.font = `36px MFMengYuan-Regular`;
context.fillText('前端侦探', width/2, height/2);
// 如果前后数据统一,阐明字体还没加载胜利,持续检测
var dataNow = context.getImageData(0, 0, width/4, height/2).data;
if ([].slice.call(dataNow).join('') == [].slice.call(dataDefault).join('')) {console.log('没有变动,从新渲染')
requestAnimationFrame(detect);
}
};
这样就能够实时绘制非凡字体了
Canvas
自身就是图片了,间接能够转换成图片或者导出,这里就不多介绍了。
残缺实现过程能够查看以下链接:
- Canvas font (codepen.io)
- Canvas font (runjs.work)
四、总结一下各自优缺点
上面简略整顿了一下各自实现的难易水平
CSS
绘制最简略,尤其是在文本排版方面,要远远当先其余两种形式
SVG
绘制绝对比较简单,在矢量图形处理,比方描边特效要比 CSS 更有劣势,这两种形式导出的难点在于一些外链资源的额定解决。
而 Canvas
绘制略微简单一些,在非凡字体须要逐帧去检测是否渲染,长处是绘制进去就是图片,无需额定导出
绘制 | 导出 | |
---|---|---|
CSS | ⭐️⭐️⭐️(简略) | ⭐️⭐️(个别) |
SVG | ⭐️⭐️(个别) | ⭐️⭐️(个别) |
Canvas | ⭐️(简单) | ⭐️⭐️⭐️⭐️(超级简略) |
对于 CSS 和 SVG 的抉择能够看理论文本排版需要,比方文本须要换行,字号大小也不统一,像这种状况 CSS 就比拟有劣势了,无需去准确计算文本坐标
另外,在理论工作中,依据需要可能须要多种形式联合应用,也就是预览状态和导出状态别离用不同的形式实现,比方图片混合,在预览状态齐全能够通过 CSS 实现,在导出时才通过 Canvas 去绘制合成
心愿这几种形式能够带来一些启发,最初,如果感觉还不错,对你有帮忙的话,欢送点赞、珍藏、转发❤❤❤
欢送关注我的公众号:前端侦探