乐趣区

关于前端:CSSSVGCanvas对特殊字体的绘制与导出

欢送关注我的公众号:前端侦探

最近在我的项目中须要对非凡字体进行绘制与导出,如下

简略解释一下:所谓绘制,就是视觉上能够看到就行(预览状态),导出呢,就是将看到的转换成图片(或者 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 去绘制合成

心愿这几种形式能够带来一些启发,最初,如果感觉还不错,对你有帮忙的话,欢送点赞、珍藏、转发❤❤❤

欢送关注我的公众号:前端侦探

退出移动版