Skip to main content

HTML5 canvas 文本教程

Canvas 没有文本元素。浏览器为你提供了 fillText(),它会在某个位置绘制一个字符串,然后将其遗忘——没有换行,没有对齐方式,也无法查询它的宽度。Konva.Text 是一个形状,因此文本会作为一个节点保留下来,你可以移动、设置样式、测量它,并进行命中测试。

const text = new Konva.Text({
x: 20,
y: 20,
text: 'Hello Konva',
fontSize: 24,
fontFamily: 'Arial',
fill: 'black',
});

设置 width 后,文本会根据该宽度换行。不设置 width 时,形状会根据内容自动调整大小。

有关属性和方法的完整列表,请参阅 Text API 参考

import Konva from 'konva';

const stage = new Konva.Stage({
container: 'container',
width: window.innerWidth,
height: window.innerHeight
});

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

// 简单文本
const simpleText = new Konva.Text({
x: stage.width() / 2,
y: 15,
text: '简单文本',
fontSize: 30,
fontFamily: 'Calibri',
fill: 'green'
});

simpleText.offsetX(simpleText.width() / 2);

// 复杂文本与背景
const complexText = new Konva.Text({
x: 20,
y: 60,
text: "复杂文本\n\n整个世界都是一个舞台,所有的男人和女人只是演员。他们有他们的退场和入场。",
fontSize: 18,
fontFamily: 'Calibri',
fill: '#555',
width: 300,
padding: 20,
align: 'center'
});

const rect = new Konva.Rect({
x: 20,
y: 60,
stroke: '#555',
strokeWidth: 5,
fill: '#ddd',
width: 300,
height: complexText.height(),
shadowColor: 'black',
shadowBlur: 10,
shadowOffsetX: 10,
shadowOffsetY: 10,
shadowOpacity: 0.2,
cornerRadius: 10
});

layer.add(rect);
layer.add(simpleText);
layer.add(complexText);

测量文本

Konva.Text 在创建后会立即测量自身,因此你可以在它位于图层上之前读取其大小。

const text = new Konva.Text({ text: 'Hello Konva', fontSize: 24 });

text.width(); // full shape width, including padding
text.height(); // full shape height, all lines, including padding
text.getTextWidth(); // width of the widest line, excluding padding
text.fontSize(); // height of a single line

请使用这些方法,而不是进行估算。手动居中是人们通常进行猜测的原因:

text.offsetX(text.width() / 2);   // exact

有两点需要了解:

  • **getTextHeight() 已弃用。**它会在控制台中发出警告。对于整个形状,请使用 height();对于单行,请使用 fontSize()
  • measureSize(string) 用于测量尚未绘制的字符串,使用的是此形状的字体。它适用于在确定尺寸之前进行预先测量,但无法处理多行文本
const { width } = text.measureSize('Some other string');

在 React 和 Vue 中,请通过节点的 ref 读取相同的值,而不是进行近似计算——或者像上面的示例一样,为文本设置 widthalign,从而完全避免测量。

换行与省略号

文本仅在设置了 width 时才会换行。

new Konva.Text({
text: 'A long line that will not fit on one row',
width: 200, // wrapping requires this
wrap: 'word', // 'word' (default), 'char', or 'none'
ellipsis: true, // needs a height too, or there is nothing to overflow
});

当文本无法放入框中时,ellipsis 会使用 进行截断。只有同时设置了 widthheight 时它才会生效,因为未设置高度时,形状会扩展以适应内容,不会发生溢出。

import Konva from 'konva';

const stage = new Konva.Stage({ container: 'container', width: 500, height: 260 });
const layer = new Konva.Layer();
stage.add(layer);

const sample = 'Konva.Text wraps to the width you give it, and can truncate when it runs out of room.';

[
{ y: 10, label: "wrap: 'word' (default)", config: { width: 220 } },
{ y: 95, label: "wrap: 'char'", config: { width: 220, wrap: 'char' } },
{ y: 180, label: 'ellipsis with a fixed height', config: { width: 220, height: 44, ellipsis: true } },
].forEach(({ y, label, config }) => {
layer.add(new Konva.Text({ x: 250, y, text: label, fontSize: 13, fill: '#666' }));
layer.add(
new Konva.Rect({ x: 20, y, width: 220, height: config.height || 70, stroke: '#ddd' })
);
layer.add(new Konva.Text({ x: 20, y, text: sample, fontSize: 14, padding: 4, ...config }));
});

文本与 Web 字体

这是最常见的文本问题,而且并不是 Konva 的问题。

当 Web 字体加载完成后,DOM 元素会自行重新布局。而 Canvas 不会。如果文本是在字体加载完成之前创建的,那么它会使用备用字体进行测量,而换行、居中和 width() 仍然基于这个过时的测量结果。文本通常看起来是正确的,但位置却不对。

等待字体加载完成,然后强制重新测量:

await document.fonts.load('16px "Roboto"');

// Re-setting any measured attribute re-runs the measurement.
text.fontFamily('Roboto');

每当文本相关属性发生变化时,Konva 都会重新测量:textfontFamilyfontSizefontStylefontVariantlineHeightletterSpacingalignverticalAlignpaddingwidthheightwrapellipsisdirection。将其中任意一个属性设置为它当前已有的值也足以触发重新测量。

Custom Font 中提供了一个完整示例,其中也包括字体本身的加载