Canvas 文本绘制:字体、对齐与测量

图形只是 Canvas 的一半,另一半是文本:标题、标签、坐标轴刻度、提示信息等等。
这一篇聚焦文本相关能力,围绕几个问题展开:如何控制字体与对齐方式、如何在高 DPI 下保持文字清晰、以及如何用文本测量来做简单布局。

Canvas 里用于绘制文本的两个基础方法是:

  • fillText(text, x, y):使用当前填充样式绘制实心文本;
  • strokeText(text, x, y):使用当前描边样式绘制描边文本。

文本样式主要由以下属性控制:

  • font:类似 CSS 的字体声明,例如 "16px sans-serif"
  • fillStyle / strokeStyle:文字颜色;
  • textAlign:水平方向对齐方式;
  • textBaseline:垂直方向基线对齐方式。

一个简单示例:

1ctx.font = "16px sans-serif";
2ctx.fillStyle = "#333";
3ctx.textAlign = "left";      // 默认
4ctx.textBaseline = "alphabetic"; // 默认
5ctx.fillText("Hello Canvas", x, y);

理解这些属性的组合,是后续做更复杂文本布局的基础。

textAlign 决定 x 相对于文本的水平位置:

  • "left"x 在文本左侧;
  • "right"x 在文本右侧;
  • "center"x 在文本水平中点;
  • "start" / "end":根据文本方向决定,对大多数左到右语言,等价于 "left" / "right"

在绘制坐标轴刻度、按钮标签等场景中,合理使用水平对齐可以减少手动计算偏移。

textBaseline 决定 y 相对于文本垂直位置的关系:

  • "alphabetic"(默认):y 对应普通西文字母基线;
  • "top" / "hanging" / "middle" / "ideographic" / "bottom":分别对应不同的基线风格。

在实践中:

  • 使用 "middle" 搭配 textAlign: 'center' 可以方便地将文本居中放在某个点;
  • 在多语言场景,需要根据字体和排版需求选择合适的基线。

在高 DPI 屏幕上,如果 Canvas 没有按 devicePixelRatio 调整尺寸,文本会被放大插值,显得发糊。
解决方案与图形类似:

  • Canvas 的实际像素尺寸按 devicePixelRatio 放大;
  • 使用 scale(dpr, dpr) 调整坐标系;
  • 文本大小仍按逻辑像素指定(例如 "16px")。

这样可以:

  • 让文本以更高的物理像素精度绘制;
  • 避免浏览器对整个 Canvas 进行二次缩放。

在需要极高清晰度的场景(如小字体标注),可以适当增大小号字体并控制缩放,结合实际观察进行调整。

ctx.measureText(text) 返回一个 TextMetrics 对象,至少包含:

  • width:在当前 fonttextAligntextBaseline 下文本的宽度;

在简单布局中,可以利用这个宽度来:

  • 实现单行文本的居中或右对齐(不依赖 textAlign 时);
  • 计算文本与其他图形的间距;
  • 做「超出宽度则截断/省略」的逻辑。

例如,一个简单的「在矩形中水平居中文本」可以是:

1const metrics = ctx.measureText(text);
2const textWidth = metrics.width;
3const x = rectX + (rectWidth - textWidth) / 2;
4const y = rectY + rectHeight / 2; // 再配合 textBaseline = 'middle'
5
6ctx.textBaseline = "middle";
7ctx.fillText(text, x, y);

对于多行文本与更精细布局:

  • 通常需要在 JS 侧做文本分行和排版;
  • measureText 可以用来估算每一行可容纳的字符数或宽度。

在很多图形场景中,文本不会单独存在,而是与矩形、圆形等图形配合使用:

  • 按钮标签;
  • 图表刻度与轴标签;
  • 节点文字(流程图、脑图等)。

工程实践中的几点建议:

  • 在封装组件时,把「绘制背景」和「绘制文本」拆成两个函数;
  • 利用 measureText 确定文本实际宽度,再给背景图形预留适量 padding;
  • 保持对齐逻辑在一个地方集中管理(例如一个 drawLabel 工具函数)。

这样可以避免到处重复写对齐和间距计算,也方便后续统一调整样式。

这一篇可以概括为几个关键点:

  • Canvas 提供了 fillText / strokeText 和一组字体/对齐属性,让可以在任意位置绘制文本;
  • textAligntextBaseline 决定了 (x, y) 在文字中的含义,是做布局和对齐时必须搞清楚的;
  • 高 DPI 下需要配合整体 Canvas 尺寸缩放来保持文字清晰;
  • measureText 是做单行文本布局的基础工具,复杂排版则需要在 JS 中自行分行和定位。

在掌握这些基础之后,Canvas 中的文字不再只是简单的标注,而可以参与到更复杂的布局与交互设计中。