解决在 Canvas 中使用自定义字体初次绘制文字时不生效的问题

4,736 阅读2分钟

原文链接: github.com/yinxin630/b…
技术交流: fiora.suisuijiang.com/

请尝试如下代码, 需要先下载字体 d.xiazaiziti.com/en_fonts/fo…

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <style>
      @font-face {
        font-family: "Abril Fatface";
        font-style: normal;
        font-weight: 400;
        src: url("./abril-fatface-v9-latin-regular.ttf") format("truetype");
      }
    </style>
  </head>
  <body>
    <canvas width="600" height="400"></canvas>
    <script>
      const $canvas = document.querySelector("canvas");
      const ctx = $canvas.getContext("2d");
      ctx.font = "32px Abril Fatface";
      ctx.fillText("Draw text in canvas with special font", 20, 100);
    </script>
  </body>
</html>

image
如图, 自定义字体并没有生效. 但如果尝试在初次绘制后, 延时一段时间再次绘制, 就没问题

ctx.fillText("Draw text in canvas with special font", 20, 100);
setTimeout(() => {
  ctx.clearRect(0, 0, 600, 400);
  ctx.fillText("Draw text in canvas with special font", 20, 100);
}, 500);

问题原因是因为我们所用的字体需要异步加载, 它是在初次绘制文字时才开始加载的. 因为在初次绘制时, 字体还没有加载完毕, 所以会使用默认字体渲染

浏览器提供了检查相应字体是否加载完成的 API. document.fonts.check()

ctx.font = "32px Abril Fatface";
console.log(document.fonts.check(ctx.font)); // false
ctx.fillText("Draw text in canvas with special font", 20, 100);

浏览器还提供了等待字体加载完成和主动加载字体的 API. document.fonts.ready / document.fonts.load(). 我们可以直接触发字体加载, 然后等待字体加载完毕后, 再在 canvas 中绘制文本. 就可以解决问题了

async function drawText() {
  ctx.font = "32px Abril Fatface";
  await document.fonts.load(ctx.font);
  ctx.fillText("Draw text in canvas with special font", 20, 100);
}
drawText();

image

另外你还可以通过 js 而不是 css 来加载字体, 这样会更方便些

async function drawText() {
  const AbrilFatface = new FontFace('Abril Fatface', 'url(./Abril-Fatface.ttf)', { style: 'normal', weight: 400 });
  await AbrilFatface.load();
  ctx.font = "32px Abril Fatface";
  ctx.fillText("Draw text in canvas with special font", 20, 100);
}
drawText();