跳到主要内容

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);

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

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

// Complex text with background
const complexText = new Konva.Text({
x: 20,
y: 60,
text: "COMPLEX TEXT\n\nAll the world's a stage, and all the men and women merely players. They have their exits and their entrances.",
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 才会生效, 因为没有 height 时图形会自动增高以容纳内容,永远不会溢出。

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。即使把其中某个属性 设置为它原本的值,也足以触发。

完整示例(包括如何加载字体本身)参见自定义字体