跳到主要内容

如何在 HTML5 画布中使用自定义字体?

如何在 HTML5 画布上绘制外部字体?

如果你想为 Konva.Text 使用自定义字体,只需:

  1. 将字体样式添加到页面中
  2. 当字体加载完成后,将 fontFamily 属性设置为所需的字体名称

但这里有一个重要事项。当你为 DOM 元素(如 divspan)设置字体时,浏览器会在字体加载后自动更新这些元素。但对于画布文本,情况则不同。你需要重新绘制画布。

注意: 对于不支持原生字体加载 API 的旧版浏览器,你可以使用宽度测量方法 - 先用备用字体测量文本宽度,然后定期检查自定义字体的宽度是否发生变化(表明字体已加载)。

加载字体

你可以使用这个异步函数,它结合了原生字体加载 API 和可靠的宽度测量回退及时间保护:

const loadedFonts = {};

function measureFont(fontName, fallbackFont, fontStyle = 'normal', fontWeight = '400') {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
const sampleText = 'The quick brown fox 0123456789';
ctx.font = `${fontStyle} ${fontWeight} 16px '${fontName}', ${fallbackFont}`;
return ctx.measureText(sampleText).width;
}

function delay(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}

async function loadFont(fontName, fontStyle = 'normal', fontWeight = '400') {
if (loadedFonts[fontName]) return;

const hasFontsLoadSupport = !!(document.fonts && document.fonts.load);
const arialWidth = measureFont('Arial', 'Arial', fontStyle, fontWeight);

if (hasFontsLoadSupport) {
try {
await document.fonts.load(`${fontStyle} ${fontWeight} 16px '${fontName}'`);
const newWidth = measureFont(fontName, 'Arial', fontStyle, fontWeight);
const shouldTrustChanges = arialWidth !== newWidth;
if (shouldTrustChanges) {
// 添加短暂延迟以避免罕见的指标尚未准备好的竞态情况
await delay(60);
loadedFonts[fontName] = true;
return;
}
} catch (e) {
// 忽略错误并回退到轮询方法
}
}

const timesWidth = measureFont('Times', 'Times', fontStyle, fontWeight);
const lastWidth = measureFont(fontName, 'Arial', fontStyle, fontWeight);
const waitTime = 60;
const timeout = 6000; // 最多等待6秒
const attemptsNumber = Math.ceil(timeout / waitTime);
for (let i = 0; i < attemptsNumber; i++) {
const newWidthArial = measureFont(fontName, 'Arial', fontStyle, fontWeight);
const newWidthTimes = measureFont(fontName, 'Times', fontStyle, fontWeight);
const somethingChanged =
newWidthArial !== lastWidth ||
newWidthArial !== arialWidth ||
newWidthTimes !== timesWidth;
if (somethingChanged) {
await delay(60);
loadedFonts[fontName] = true;
return;
}
await delay(waitTime);
}
console.warn(
`加载字体 "${fontName}" 超时。这是一个正确的字体族吗?`
);
}
import Konva from 'konva';

const loadedFonts = {};

function measureFont(fontName, fallbackFont, fontStyle = 'normal', fontWeight = '400') {
  const canvas = document.createElement('canvas');
  const ctx = canvas.getContext('2d');
  const sampleText = 'The quick brown fox 0123456789';
  ctx.font = `${fontStyle} ${fontWeight} 16px '${fontName}', ${fallbackFont}`;
  return ctx.measureText(sampleText).width;
}

function delay(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

async function loadFont(fontName, fontStyle = 'normal', fontWeight = '400') {
  if (loadedFonts[fontName]) return;

  const hasFontsLoadSupport = !!(document.fonts && document.fonts.load);
  const arialWidth = measureFont('Arial', 'Arial', fontStyle, fontWeight);

  if (hasFontsLoadSupport) {
    try {
      await document.fonts.load(`${fontStyle} ${fontWeight} 16px '${fontName}'`);
      const newWidth = measureFont(fontName, 'Arial', fontStyle, fontWeight);
      const shouldTrustChanges = arialWidth !== newWidth;
      if (shouldTrustChanges) {
        await delay(60);
        loadedFonts[fontName] = true;
        return;
      }
    } catch (e) {}
  }

  const timesWidth = measureFont('Times', 'Times', fontStyle, fontWeight);
  const lastWidth = measureFont(fontName, 'Arial', fontStyle, fontWeight);
  const waitTime = 60;
  const timeout = 6000;
  const attemptsNumber = Math.ceil(timeout / waitTime);
  for (let i = 0; i < attemptsNumber; i++) {
    const newWidthArial = measureFont(fontName, 'Arial', fontStyle, fontWeight);
    const newWidthTimes = measureFont(fontName, 'Times', fontStyle, fontWeight);
    const somethingChanged =
      newWidthArial !== lastWidth ||
      newWidthArial !== arialWidth ||
      newWidthTimes !== timesWidth;
    if (somethingChanged) {
      await delay(60);
      loadedFonts[fontName] = true;
      return;
    }
    await delay(waitTime);
  }
  console.warn(`加载字体 "${fontName}" 超时。`);
}

// 使用样式表链接加载字体
const fontLink = document.createElement('link');
fontLink.href = 'https://fonts.googleapis.com/css2?family=Kavivanar&display=swap';
fontLink.rel = 'stylesheet';
document.head.appendChild(fontLink);

// 立即使用备用字体构建舞台
var width = window.innerWidth;
var height = window.innerHeight;

var stage = new Konva.Stage({
  container: 'container',
  width: width,
  height: height,
});

var layer = new Konva.Layer();
stage.add(layer);

var text = new Konva.Text({
  x: 50,
  y: 50,
  fontSize: 40,
  text: 'A text with custom font.',
  width: 250,
  fontFamily: 'Arial'
});

layer.add(text);

// 然后等待字体加载并应用
loadFont('Kavivanar', 'normal', '400').then(() => {
  text.fontFamily('Kavivanar');
});