二维码

第二章 绘制文字

本章摘要

本章节将介绍如何使用HTML5 Canvas API绘制和美化文字,包括设置颜色、字体、对齐方式,以及调整间距、添加阴影和实现垂直排列等高级技巧。通过学习,您将掌握Canvas中文本渲染的核心技术,提升图形开发能力,满足各种复杂的文本渲染需求。

图形系统开发实战课程 - 基础篇 绘制文字

第二章 绘制文字

本章的内容包括:

  • 绘制常见的文本
  • 文本的水平对齐
  • 文本的垂直对齐
  • 文本的间距
  • 文本垂直排列

1. 绘制常见的文本

画布渲染上下文提供了两个api用于绘制文本:

1
2
ctx.strokeText(text, x, y[, maxWidth]);
ctx.fillText(text, x, y[, maxWidth]);

  strokeText()是用于绘制文本外框,而fillText()是填充文本。我们先看一下绘制结果:

执行效果

绘制填充文字

  我们日常见到的文字就是填充的文字,通过fillText()方法,就能够直接在画布中绘制填充的文字。其代码为:

1
2
ctx.font = "40px 黑体";
ctx.fillText("绘制普通中文和English文字", 50, 80);

  Canvas通过font属性指定字体大小和字体名称,其格式和css样式中用法的一致,这里的文字大小通常指的就是文字的高度,其单位为像素(px)。

绘制空心文字

  在画布中绘制空心文字也很简单,只需通过strokeText()方法即可实现,其代码如下:

1
2
ctx.font = "40px 黑体";
ctx.strokeText("绘制空心中文和English文字", 50, 150);

绘制空心文字还可以通过线宽lineWidth属性指定边框的大小,其效果如下图所示:
执行效果

源代码如下:

1
2
3
4
5
6
7
8
// 绘制文字边框
ctx.font = "40px 黑体";
ctx.lineWidth = 1;
ctx.strokeText("边框粗细", 60, 60);
ctx.lineWidth = 2;
ctx.strokeText("边框粗细", 260, 60);
ctx.lineWidth = 3;
ctx.strokeText("边框粗细", 460, 60);

指定字体的其他样式

  渲染上下文对象还提供了一些属性可以设置字体、字号、粗体、斜体、文字颜色等样式。

属性名称 说明
ctx.font 设置字体属性,包括字体、字号、粗细、斜体等样式
ctx.fillStyle 设置字体颜色
ctx.strokeStyle 设置在绘制空心文字时边框的大小

下面这个示例设置几种字体和字号,并设置了粗体和斜体,其效果如下图所示:

执行效果

源代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<script>
// 从页面中获取画板对象
let canvas = document.getElementById('canvas');
// 从画板中获取“2D渲染上下文”对象
let ctx = canvas.getContext('2d');
// 绘制背景网格线
drawGrid("lightgray", 10, 10);

// 绘制填充的文字
ctx.font = "40px 黑体";
ctx.fillText("40px黑体", 40, 60);
ctx.font = "30px 楷体";
ctx.fillText("30px楷体", 280, 60);
ctx.fillStyle = "red";
ctx.font = "bold 22px 仿宋";
ctx.fillText("22px加粗仿宋", 460, 60);
ctx.font = "italic 22px 仿宋";
ctx.fillText("22px斜体仿宋", 640, 60);

// 绘制空心的文字
ctx.font = "italic 40px 黑体";
ctx.strokeText("40px黑体", 40, 130);
ctx.font = "30px 楷体";
ctx.strokeText("30px楷体", 280, 130);
ctx.strokeStyle = "blue";
ctx.font = "22px 仿宋";
ctx.strokeText("22px仿宋", 460, 130);
ctx.font = "16px 仿宋";
ctx.strokeText("16px仿宋", 640, 130);
</script>

以下为设置字体为粗体或斜体的写法:
将字体设置为粗体: ctx.font = "bold 22px 仿宋"
将字体设置为斜体: ctx.font = "italic 22px 仿宋"
将字体设置为加粗和斜体: ctx.font = "bold italic 22px 仿宋"

绘制带阴影的文字

  画布渲染上下文直接提供了shadowColorshadBlur等属性,可实现绘制带阴影的文字,这几个属性的说明如下:

属性 说明
shadowBlur 模糊效果程度
shadowColor 阴影颜色
shadowOffsetX 阴影水平偏移距离,默认值是 0。
shadowOffsetY 阴影垂直偏移距离,默认值是 0。

下面这个示例绘制了几个带阴影的文字,其效果如下图所示:

执行效果

源代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<script>
// 从页面中获取画板对象
let canvas = document.getElementById('canvas');
// 从画板中获取“2D渲染上下文”对象
let ctx = canvas.getContext('2d');
// 绘制背景网格线
drawGrid("lightgray", 10, 10);

// 绘制带阴影的文字
ctx.save();
ctx.shadowColor = "#000000";
ctx.shadowBlur = 8;
ctx.font = "40px 黑体";
ctx.fillText("文字阴影效果1", 50, 70);

// 改变阴影模糊程度
ctx.shadowBlur = 4;
ctx.fillText("文字阴影效果2", 420, 70);

// 设置阴影水平偏移量
ctx.shadowColor = "#FF0000";
ctx.shadowOffsetX = 3;
ctx.fillText("文字阴影效果3", 50, 140);

// 设置阴影垂直偏移量
ctx.shadowOffsetY = 3;
ctx.fillText("文字阴影效果4", 420, 140);
</script>

绘制带背景的文字

  带背景的文字其实是绘制了一次空心文字和一次填充文字,且在绘制空心文字时需要将线宽指定大一点,其代码如下:

1
2
3
4
5
6
ctx.font = "22px 黑体";
ctx.lineWidth = 5;
ctx.strokeStyle = "#CCCCCC";
ctx.strokeText("带背景的文字", 100, 310);
ctx.fillStyle="#000000";
ctx.fillText("带背景的文字", 100, 310);

  带背景的文字通常应用在背景比较复杂的图形中,例如地理图或包含了多种颜色的图像,这类图形有可能是浅色背景,也有可能是深色背景,给文字添加了背景后,就能够直观突出文字了,如下图中的滤镜名称就应用了这种文字效果。

执行效果


2. 水平对齐

  在office word中排版时,我们经常会用到文字水平对齐功能,Canvas也提供了类似的功能(其实word本质上也是一个使用绘图功能开发出来的应用软件)。画布渲染上下文提供了textAlign属性,可指定文字的水平对齐方式,该属性可选择以下几个值:

属性 说明
left 文本左对齐
right 文本右对齐
center 文本居中对齐
start 默认值,文本对齐界线开始的地方(左对齐指本地从左向右,右对齐指本地从右向左)
end 文本对齐界线结束的地方(左对齐指本地从左向右,右对齐指本地从右向左)

我们看一下各种水平对齐方式的运行效果,如下图所示:

执行效果

textAlign=“left”

  文本左对齐比较好理解,指的是在水平方向从给定的x坐标值开始,将文字字符串从左向右延伸排列。注意上图中红点表示的坐标(x,y)位置。

1
2
3
// 绘制各种水平对齐的文本
ctx.textAlign = "left";
ctx.fillText("文字的水平对齐(left)", 300, 60);

textAlign=“center”

  文本居中对齐指的是在水平方向从给定的x坐标值开始,将文字字符串居中向两端排列(也就是说文本一半在x的左边,一半在x的右边)。注意上图中红点表示的坐标(x,y)位置。

1
2
3
// 绘制各种水平对齐的文本
ctx.textAlign = "center";
ctx.fillText("文字的水平对齐(center)", 300, 110);

textAlign=“right”

  文本右对齐指的是在水平方向从给定的x坐标值开始,将文字字符串从右向左延伸排列。注意上图中红点表示的坐标(x,y)位置。

1
2
3
// 绘制各种水平对齐的文本
ctx.textAlign = "right";
ctx.fillText("文字的水平对齐(right)", 300, 160);

textAlign=“start”

  该值为默认缺省值,当没有设置文字方向ctx.direction属性或ctx.direction="ltr"时,其运行效果与ctx.textAlign = "left"一样;当ctx.direction="rtl"时,其运行效果与ctx.textAlign = "right"一样。

1
2
3
4
5
6
// 绘制各种水平对齐的文本
ctx.textAlign = "start";
ctx.fillText("文字的水平对齐(start)", 300, 230);
ctx.direction = "rtl";
ctx.textAlign = "start";
ctx.fillText("文字的水平对齐(start)", 300, 350);

textAlign=“end”

  当没有设置文字方向ctx.direction属性或ctx.direction="ltr"时,其运行效果与ctx.textAlign = "right"一样;当ctx.direction="rtl"时,其运行效果与ctx.textAlign = "left"一样。

1
2
3
4
5
ctx.textAlign = "end";
ctx.fillText("文字的水平对齐(end)", 300, 280);
ctx.direction = "rtl";
ctx.textAlign = "end";
ctx.fillText("文字的水平对齐(end)", 300, 400);

这部分的完整代码如下:

{.line-numbers}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
<script>
// 从页面中获取画布对象
let canvas = document.getElementById('canvas');
// 从画布中获取“2D渲染上下文”对象
let ctx = canvas.getContext('2d');
// drawGrid
drawGrid("lightgray", 10, 10);

// 设置字体属性
ctx.font = "30px Arial, sans-serif";

// 绘制各种水平对齐的文本
ctx.textAlign = "left";
ctx.fillText("文字的水平对齐(left)", 300, 60);
ctx.textAlign = "center";
ctx.fillText("文字的水平对齐(center)", 300, 110);
ctx.textAlign = "right";
ctx.fillText("文字的水平对齐(right)", 300, 160);

ctx.textAlign = "start";
ctx.fillText("文字的水平对齐(start)", 300, 230);
ctx.textAlign = "end";
ctx.fillText("文字的水平对齐(end)", 300, 280);

ctx.direction = "rtl";
ctx.textAlign = "start";
ctx.fillText("文字的水平对齐(start)", 300, 350);
ctx.textAlign = "end";
ctx.fillText("文字的水平对齐(end)", 300, 400);

// 绘制辅助线
ctx.beginPath();
ctx.moveTo(300, 0); ctx.lineTo(300, canvas.height);
ctx.moveTo(0, 180); ctx.lineTo(canvas.width, 180);
ctx.moveTo(0, 300); ctx.lineTo(canvas.width, 300);
ctx.strokeStyle="green";
ctx.stroke();

// 绘制辅助文字
ctx.font = "16px Arial, sans-serif";
ctx.textAlign="left";
ctx.fillStyle="blue";
ctx.fillText('(ctx.direction = 默认值)', 5, 40);
ctx.fillText('(ctx.direction = "ltr")', 5, 200);
ctx.fillText('(ctx.direction = "rtl")', 5, 420);

// 绘制坐标点
ctx.beginPath();
ctx.arc(300, 60, 4, 0, 2 * Math.PI);
ctx.arc(300, 110, 4, 0, 2 * Math.PI);
ctx.arc(300, 160, 4, 0, 2 * Math.PI);
ctx.arc(300, 210, 4, 0, 2 * Math.PI);
ctx.arc(300, 260, 4, 0, 2 * Math.PI);
ctx.arc(300, 350, 4, 0, 2 * Math.PI);
ctx.arc(300, 400, 4, 0, 2 * Math.PI);
ctx.fillStyle = "red";
ctx.fill();
</script>


3. 垂直对齐

我们先来了解一下六线五格图,如下图所示:
六线五格图

  六线五格图用于指定一个文本和绘制点之间的关系,除了字母f外其他所有的文字都被限制在该图内,“六线五格”是几乎所有绘图绘制文本的基线图。
  Canvas也不例外,Canvas通过设置文字的基线(textBaseline)属性,实现文字的垂直对齐。textBaseline属性包括:

属性 说明
alphabetic 默认,文本基线是标准的字母基线
top 文本基线在文本块的顶部
hanging 文本基线是悬挂基线
middle 文本基线在文本块的中间
ideographic 文字基线是表意字基线
bottom 文本基线在文本块的底部

基线是指英文(拉丁文)或西亚文字排版中,用于在上面放置字符的一条假想的基准线,而中文和其他东亚文字没有基线。

先看一看运行效果:
运行效果
说明:图中的红点为fillText()的坐标点,这里的水平对齐采用的是默认靠左的水平对齐方式。

textBaseline=“alphabetic”(默认值)

  文本基线是标准的字母基线,这种方式在英文中比较常见,而中文似乎没多大意义了。注意上图中红点表示的坐标(x,y)位置。

1
2
ctx.textBaseline = "alphabetic";
ctx.fillText("4 文字垂直对齐(alphabetic, abcdefghijklmnopqrstuvwxyz)", 60, 290);

textBaseline=“top”

  文本基线在文本块的顶部,这就是我们常说的顶端对齐,在中文中比较好理解,在英文中还需区分hanging的对齐方式。注意上图中红点表示的坐标(x,y)位置。

1
2
ctx.textBaseline = "top";
ctx.fillText("1 文字垂直对齐(top, abcdefghijklmnopqrstuvwxyz)", 60, 50);

textBaseline=“middle”

  文本基线在文本块的中间,也就是垂直居中对齐,对于中英文都一样。注意上图中红点表示的坐标(x,y)位置。

1
2
ctx.textBaseline = "middle";
ctx.fillText("3 文字垂直对齐(middle, abcdefghijklmnopqrstuvwxyz)", 60, 210);

textBaseline=“bottom”

  文本基线在文本块的底部,这就是我们常说的底端对齐,在中文中比较好理解,在英文中还需区分ideographic的对齐方式。注意上图中红点表示的坐标(x,y)位置。

1
2
ctx.textBaseline = "bottom";
ctx.fillText("6 文字垂直对齐(bottom, abcdefghijklmnopqrstuvwxyz)", 60, 450);

textBaseline=“hanging”

  文本基线是悬挂基线

1
2
ctx.textBaseline = "hanging";
ctx.fillText("2 文字垂直对齐(hanging, abcdefghijklmnopqrstuvwxyz)", 60, 130);

textBaseline=“ideographic”

  文字基线是表意字基线

1
2
ctx.textBaseline = "ideographic";
ctx.fillText("5 文字垂直对齐(ideographic, abcdefghijklmnopqrstuvwxyz)", 60, 370);

这部分的完整代码如下:

{.line-numbers}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
<script>
// 从页面中获取画布对象
let canvas = document.getElementById('canvas');
// 从画布中获取“2D渲染上下文”对象
let ctx = canvas.getContext('2d');
// drawGrid
drawGrid("lightgray", 10, 10);
// 设置字体属性
ctx.font = "30px sans-serif";

// 绘制各种垂直对齐文本
ctx.textBaseline = "top";
ctx.fillText("1 文字垂直对齐(top, abcdefghijklmnopqrstuvwxyz)", 100, 100);
ctx.textBaseline = "hanging";
ctx.fillText("2 文字垂直对齐(hanging, abcdefghijklmnopqrstuvwxyz)", 100, 200);
ctx.textBaseline = "middle";
ctx.fillText("3 文字垂直对齐(middle, abcdefghijklmnopqrstuvwxyz)", 100, 300);
ctx.textBaseline = "alphabetic";
ctx.fillText("4 文字垂直对齐(alphabetic, abcdefghijklmnopqrstuvwxyz)", 100, 400);
ctx.textBaseline = "ideographic";
ctx.fillText("5 文字垂直对齐(ideographic, abcdefghijklmnopqrstuvwxyz)", 100, 500);
ctx.textBaseline = "bottom";
ctx.fillText("6 文字垂直对齐(bottom, abcdefghijklmnopqrstuvwxyz)", 100, 600);

// 绘制辅助线
ctx.beginPath();
ctx.moveTo(0, 100); ctx.lineTo(canvas.width, 100);
ctx.moveTo(0, 200); ctx.lineTo(canvas.width, 200);
ctx.moveTo(0, 300); ctx.lineTo(canvas.width, 300);
ctx.moveTo(0, 400); ctx.lineTo(canvas.width, 400);
ctx.moveTo(0, 500); ctx.lineTo(canvas.width, 500);
ctx.moveTo(0, 600); ctx.lineTo(canvas.width, 600);
ctx.strokeStyle="green";
ctx.stroke();

// 绘制坐标点
ctx.beginPath();
ctx.arc(100, 100, 4, 0, 2 * Math.PI);
ctx.arc(100, 200, 4, 0, 2 * Math.PI);
ctx.arc(100, 300, 4, 0, 2 * Math.PI);
ctx.arc(100, 400, 4, 0, 2 * Math.PI);
ctx.arc(100, 500, 4, 0, 2 * Math.PI);
ctx.arc(100, 600, 4, 0, 2 * Math.PI);
ctx.fillStyle = "red";
ctx.fill();
</script>

  top与hangling非常相似,ideographic与bottom也一样,这两类在中文的垂直对齐中没有差异,英文(拉丁字母)也只是在某些字体中才会有差异,例如serif字体,请看下图:
运行效果
源代码如下:

{.line-numbers}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<script>
// 从页面中获取画布对象
let canvas = document.getElementById('canvas');
// 从画布中获取“2D渲染上下文”对象
let ctx = canvas.getContext('2d');
// drawGrid
drawGrid("lightgray", 10, 10);
// 设置字体
ctx.font = "30px serif";

// 绘制各种垂直对齐文本
ctx.textBaseline = "top";
ctx.fillText("(abcdefg)", 50, 50);
ctx.textBaseline = "hanging";
ctx.fillText("(abcdefg)", 180, 50);
ctx.textBaseline = "middle";
ctx.fillText("(abcdefg)", 310, 50);
ctx.textBaseline = "alphabetic";
ctx.fillText("(abcdefg)", 440, 50);
ctx.textBaseline = "ideographic";
ctx.fillText("(abcdefg)", 570, 50);
ctx.textBaseline = "bottom";
ctx.fillText("(abcdefg)", 700, 50);

// 垂直排布名称
ctx.font = "16px bold Arial,sans-serif";
ctx.fillStyle="blue";
ctx.fillText("textBaseline: ", 10, 125);
ctx.fillText("top", 120, 125);
ctx.fillText("hangling", 210, 125);
ctx.fillText("middle", 340, 125);
ctx.fillText("alphabetic", 450, 125);
ctx.fillText("ideographic", 580, 125);
ctx.fillText("bottom", 730, 125);

// 绘制辅助线
ctx.beginPath();
ctx.moveTo(0, 50); ctx.lineTo(canvas.width, 50);
ctx.strokeStyle = "green";
ctx.lineWidth = 2;
ctx.stroke();
</script>


4. 文字间距

设置文字最大宽度

执行效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<script>
// 从页面中获取画布对象
let canvas = document.getElementById('canvas');
// 从画布中获取“2D渲染上下文”对象
let ctx = canvas.getContext('2d');
// drawGrid
drawGrid("lightgray", 10, 10);

// 设置文字最大宽度
ctx.font = "bold 30px Inconsolata";
ctx.textAlign = "left";
ctx.textBaseline = "top";
ctx.fillText("图形系统开发实战: draw text", 60, 60);
ctx.fillText("图形系统开发实战: draw text", 60, 130, 300);
ctx.fillText("图形系统开发实战: draw text", 60, 200, 200);

// 绘制文本外框文字
ctx.strokeStyle = "red";
ctx.strokeRect(60, 130, 300, 30);
ctx.strokeRect(60, 200, 200, 30);
</script>

letterSpacing属性

  letterSpacing属性用于设置中文的字与字之间,英文单词的字母与字母之间的距离,其单位为像素(px),其执行效果如下图所示:
执行效果
其代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<script>
// 从页面中获取画布对象
let canvas = document.getElementById('canvas');
// 从画布中获取“2D渲染上下文”对象
let ctx = canvas.getContext('2d');
// drawGrid
drawGrid("lightgray", 10, 10);

// 设置字体属性
ctx.font = "bold 30px Inconsolata";

// 修改letterSpacing属性绘制文本
ctx.save();
ctx.letterSpacing = "6px";
ctx.fillText("图形系统开发实战: use letterSpacing property", 60, 50);
ctx.letterSpacing = "8px";
ctx.fillText("图形系统开发实战: use letterSpacing property", 60, 120);
ctx.letterSpacing = "10px";
ctx.fillText("图形系统开发实战: use letterSpacing property", 60, 190);
ctx.restore();
</script>

wordSpacing属性

  wordSpacing属性仅对英文有效,该属性可设置单词之间的间距,其执行效果如下图所示:
执行效果
其代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<script>
// 从页面中获取画布对象
let canvas = document.getElementById('canvas');
// 从画布中获取“2D渲染上下文”对象
let ctx = canvas.getContext('2d');
// drawGrid
drawGrid("lightgray", 10, 10);

// 设置字体属性
ctx.font = "bold 30px Inconsolata";

// 修改wordSpacing属性绘制文本
ctx.save();
ctx.wordSpacing = "10px";
ctx.fillText("图形系统开发实战: use wordSpacing property", 60, 50);
ctx.wordSpacing = "20px";
ctx.fillText("图形系统开发实战: use wordSpacing property", 60, 120);
ctx.wordSpacing = "30px";
ctx.fillText("图形系统开发实战: use wordSpacing property", 60, 190);
ctx.restore();
</script>

测量文字宽度measureText()

  画布渲染上下文提供了测量文字宽度的apimeasureText(text),该方法用于在设置font、wordSpacing、letterSpacing等属性后,可测量绘制出来的文本的宽度,其参数为字符串,不仅仅是英文字母、英文单词、英文句子、单个中文和中文句子的宽度都能够测量,使用该api就可以改变文字的坐标,将绘制字符串改变为绘制一个一个的文字,在文字之间增加空白,从而实现改变文字之间间距的目的。


5. 文本垂直排列

  英文中由于单词中包含字母数量差异非常大,因此不常采用垂直排布,如果一定需要垂直排布,也是将文字旋转90°或-90°,先看看以下的效果图:

执行效果

  这种实现方法比较简单,需要注意的是红色的原点为这两行文字的坐标点,在绘制的时候可能需要计算坐标值,画布旋转ctx.rotate()我们将会在后续章节进行讲解,这里暂时略过。其代码如下:

{.line-numbers}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<script>
// 从页面中获取画布对象
let canvas = document.getElementById('canvas');
// 从画布中获取“2D渲染上下文”对象
let ctx = canvas.getContext('2d');
// drawGrid
drawGrid("lightgray", 10, 10);

// 设置字体属性
ctx.font = "30px 黑体";
let text = "使用ctx.rotate()旋转画布, 实现文字垂直排列";

// 绘制文本1
ctx.save();
ctx.translate(50, 50);
ctx.rotate(90 * Math.PI / 180);
ctx.fillText(text, 0, 0);
let textWidth = ctx.measureText(text).width;
ctx.restore();

// 绘制文本2
ctx.save();
ctx.translate(150, 50 + textWidth);
ctx.rotate(-90 * Math.PI / 180);
ctx.fillText(text, 0, 0);
ctx.restore();

// 绘制坐标点
ctx.beginPath();
ctx.arc(50, 50, 4, 0, 2 * Math.PI);
ctx.arc(150, 50 + textWidth, 4, 0, 2 * Math.PI);
ctx.fillStyle = "red";
ctx.fill();
</script>

   在这个效果图中,对于英文的垂直排列可能就只有这种方式,但对于中文则可以采取古代书籍的垂直排列方式,如下图所示:

执行效果

  画布渲染上下文没有提供方法进行这样的垂直排布,我们可以通过一个函数实现这个功能,主要思路如下:

  • 逐字(字母/单个中文文字)分析句子中的文字,并逐字记录文字宽度;
  • 使用(x,y)作为第一个文字的坐标,同时使用measureText()测量文字宽度,y + 文字宽度 + 文字间隙 作为下一个文字的y坐标;
  • 如果是英文字母,按照上述方法把画布旋转90°绘制文字;
  • 如果是中文则逐个汉字绘制出来;

上述效果的代码如下:

{.line-numbers}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
<script>
// 从页面中获取画布对象
let canvas = document.getElementById('canvas');
// 从画布中获取“2D渲染上下文”对象
let ctx = canvas.getContext('2d');

// 设置字体属性
ctx.font = "30px 黑体";

// 开始绘制文本
fillTextVertical(ctx, "1.Word文字垂直排列", 50, 60);
fillTextVertical(ctx, "2.Word文字垂直排列", 140, 60);
</script>

<script>
// 垂直排布文字
function fillTextVertical(ctx, text, x, y) {
let canvas = ctx.canvas;
let arrText = text.split('');
let arrWidth = arrText.map(function (letter) {
return ctx.measureText(letter).width;
});

ctx.save();
let align = ctx.textAlign;
let baseline = ctx.textBaseline;

if (align == 'left' || align == 'start') {
x = x + Math.max(...arrWidth) / 2;
} else if (align == 'right') {
x = x - Math.max(...arrWidth) / 2;
}
if (baseline == 'bottom' || baseline == 'alphabetic' || baseline == 'ideographic') {
y = y - arrWidth[0] / 2;
} else if (baseline == 'top' || baseline == 'hanging') {
y = y + arrWidth[0] / 2;
}

ctx.textAlign = 'center';
ctx.textBaseline = 'middle';

// 开始逐字绘制
arrText.forEach(function (letter, index) {
// 是否需要旋转判断
let code = letter.charCodeAt(0);
if (code <= 256) {
// 英文字符,旋转90°
ctx.translate(x, y);
ctx.rotate(90 * Math.PI / 180);
ctx.translate(-x, -y);
} else if (index > 0 && text.charCodeAt(index - 1) < 256) {
// y修正
y = y + arrWidth[index - 1] / 2;
}
ctx.fillText(letter, x, y);
// 旋转坐标系还原成初始态
ctx.setTransform(1, 0, 0, 1, 0, 0);
// 确定下一个字符的y坐标位置
let letterWidth = arrWidth[index];
y = y + letterWidth;
});
ctx.restore();
};
</script>

最后我们在欣赏一篇使用垂直排布绘制的文字效果:

执行效果


6. 本章小结

  本节讲解在Canvas中绘制文字的方法,包括绘制填充文本、空心文本、设置文本的颜色和字体,设置文本的水平对齐、文本的垂直对齐、文本的间距、文本垂直排列等内容。本节内容使用了Canvas 2D API以下方法:

方法名 说明
strokeText 在指定的坐标上绘制文本字符串,并使用当前的strokeStyle进行描边
fillText 在指定的坐标上绘制文本字符串,并使用当前的 fillStyle 对其进行填充
measureText 测量文本宽度等信息

属性包括:

属性名 说明
font 描述绘制文字时,当前字体样式的属性。使用和 CSS font 规范相同的字符串值。
textAlign 文本的水平对齐方式
textBaseline 文本的垂直对齐方式
wordSpacing 设置单词之间的间距
letterSpacing 设置中文的字与字之间,英文单词的字母与字母之间的距离
strokeStyle 描述画笔(绘制图形)颜色或者样式的属性
fillStyle 描述填充时颜色和样式的属性
lineWidth 设置线段宽度的属性

练习一下

(1) 绘制文本

按照以下格式要求在Canvas中绘制你的名字:

  • 垂直居中
  • 水平居中
  • 字体大小:50px
  • 字型:黑体
  • 颜色:blue

运行效果

(2) 富文本

  富文本是指在文字排列时可以指定文字的字体、字型、颜色、粗体、字间距、行间距等信息的文字排布。

  上面几个章节讲解了字体的各种绘制效果,已经涵盖到了富文本的各个特性,但每次绘制的都是单个文字或句子,需要我们指定绘制位置(坐标)才能将文字绘制出来。画布渲染上下文有没有提供api可以将一篇文章传入进去,自动换行排布,自动显示文章中各种字体颜色等富文本信息呢?

  答案是没有这样的api,这需要我们自己编程序来实现。其解决思路也并不复杂,灵活调用上面这几个api,计算文字所在的位置、设置好字体属性就能实现。

  下面是比较常见的一段富文本,由不同颜色的文字组成一了句话,这句话的定义如下,亲爱的读者,动手试一试吧。

1
2
3
4
5
6
7
8
9
10
[
{"text":"落霞", "color":"red"},
{"text":"与"},
{"text":"孤鹜", "color":"red"},
{"text":"齐飞", wordSpace:60},
{"text":"秋水", "color":"blue"},
{"text":"共"},
{"text":"长天", "color":"blue"},
{"text":"一色"}
];

运行效果


本文为“图形开发学院”(www.graphanywhere.com)网站原创文章,遵循CC BY-NC-ND 4.0版权协议,商业转载请联系作者获得授权,非商业转载请附上原文出处链接及本声明。

历史发布版本

第1版发布时间:2023-11-01
第2版发布时间:2024-06-02