文本操作
前言
在日常生活中,我们常用语言进行表达,但是在网络中,一开始还没有图片视频等元素时,我们只能用文字来进行表达,随着网络的发展,文字逐渐的被弱化,但是文字一直承担着最晦涩难懂的信息表达,也就是用视频等其他元素表达不清楚的信息,往往还是用最原始的表达,立竿见影。
Canvas 中文本的 3 个方法
在 Canvas 中,我们依旧需要对文本进行处理,以文本来表达不同的信息,Canvas 也为我们提供了 3 种操作方法: strokeText(), fillText() 和 measureText()。
strokeText() 与 fillText()
在前面的文章中,我们介绍过 stroke() 和 fill() 方法,一个是描边,一个是填充,根据字面意思,我们也能知道 strokeText() 为文本描边,fillText() 为文本填充。
js
strokeText(text, x, y, maxWidth); // 文本描边
fillText(text, x, y, maxWidth); // 文本填充
- text: 要绘制的字符串文本;
- x: 文本最左边开始的坐标;
- y: 文本最下边的坐标;
- maxWidth: (可选参数)文本最大展示宽度,如果文本超出此宽度,则将文本压缩到这个宽度。
js
<template>
<canvas ref="cnv" width="710" height="400" style="border: 1px dashed #cccccc"></canvas>
</template>
<script setup>
import { ref, onMounted } from 'vue';
const cnv = ref();
const drawText = (ctx) => {
const text1 = '炭烤小🍊微辣';
const text2 = 'Tan Kao Xiao Cheng Wei La';
const midWidth = cnv.value.width / 2;
const text1MidWidth_normal = ctx.measureText(text1).width / 2;
ctx.font = "40px pingfang";
ctx.fillStyle = "orange";
const text1MidWidth_withFontSize = ctx.measureText(text1).width / 2;
const text2MidWidth_withFontSize = ctx.measureText(text2).width / 2;
console.log("设置font属性前 text1MidWidth_normal:" + text1MidWidth_normal);
console.log("设置font属性后 text1MidWidth_withFontSize:" + text1MidWidth_withFontSize);
// 设置font属性前 text1MidWidth_normal:30
// 设置font属性后 text1MidWidth_withFontSize:120
const x1 = midWidth - text1MidWidth_withFontSize;
const x2 = midWidth - text2MidWidth_withFontSize;
ctx.fillText(text1, x1, 120);
ctx.fillText(text2, x2, 280);
ctx.setLineDash([2,2]);
ctx.strokeStyle="#2b2b2b";
ctx.beginPath();
ctx.moveTo(x1, 0);
ctx.lineTo(x1, 120);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(0, 120);
ctx.lineTo(x1, 120);
ctx.lineTo(710, 120);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(x2, 0);
ctx.lineTo(x2, 280);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(0, 280);
ctx.lineTo(x2, 280);
ctx.lineTo(710, 280);
ctx.stroke();
}
onMounted(() => {
const ctx = cnv.value.getContext('2d');
drawText(ctx);
})
</script>
上述图片中,我们根据 fillText() 方法,strokeText() 方法的定位坐标绘制的辅助线,我们可以看到,横坐标定位是精准的,但是对于中文文本,辅助线横穿文本,代表着纵坐标的左下角点位是不精准的(基于汉子框底部),对于英文文本,纵坐标几乎是精准的(针对于四线格基线)。所以我们得到的结论:fillText() 方法,strokeText() 方法的定位其实都不一定精准,但是,这其实影响不大,只是我们在精度较高的绘制时,注意这个点就好。
文本相对于容器居中
从上述代码中,我们先来看console.log() 方法打印出来的值,同样的文本"炭烤小🍊微辣",打印出得 1/2 文本长度分别是 30 和 120,结果居然不一样,看代码,我们可以得出结论:当未设置文本字体大小时(默认字体大小为 10px),获取到的文本长度为默认字体文本长度。因此我们在使用文本长度时,一定要留意 Font属性的定义。measureText(text) 在处理文本居中的时候非常有用,图片中我们也是在画布中居中,我们可以简单的总结出一个结论,文本相对于容器水平居中的坐标为
containerWidth / 2 - measureText(text).width / 2
上述代码中,我们先来介绍 measureText(text) 方法:获取 Canvas 中对应文本 text 的属性对象,对象中我们会有一个width属性是我们经常用的属性。
定位不精准问题解析
从上面我们知道,fillText(), strokeText() 定位都会出现偏差,那我们一起来了解一下,避免之后在绘制文本时踩坑。首先我们得先了解两个概念 em 框,四线格 em 框:这个很好理解,就是一个基于字体的固定尺寸框。 四线格基线:小时候都抄过英文字母吧?算了,给个图你就能明白。
我们在之前写单词的时候用到的四线格,估计大家应该都还比较有印象,fillText 方法和 strokeText 方法对于英文字母的定位就是基于四格线基线,从上面图片 "Cheng" 这个单词你可以看到,字母 "g" 是超出了定位线,这就是因为定位线为英文字母基线导致的。顺便我们提一下 em 框吧,估计可能有的小伙伴不太清楚,em 框简单来说,就是汉字设计者将汉字绘制在一个框内,框内可能有留白,不是很好理解的话,田字格,我们小时候写汉字都是在田字格内书写,但是我们在写的时候,都尽可能的把字写在格子内,所以,字体中的 em框就相当于田字格,但是,这个框其实就是设计者把所有的字都设计在这一个框内,所以汉字定位不准的原因也就是,汉字的em框可能有留白。所以我们在绘制相对精准的文本时,我们需要通过一些手段来确定,汉字的em框留白,这样我们在定位时才不会出现偏差。
Canvas 文本的的 3 个属性
font 属性
前面的代码中我们有涉及到 font 属性,我们现在就来看一下针对于 font 属性该怎么来使用。font 属性就是用于定义文本的字体样式的一个属性,其使用方式就类似于,CSS 中的 font 属性。我们来看一看具体的语法:
js
ctx.font = "font-style font-weight font-size/line-height font-family";
估计大家对 CSS 中的 font 属性都比较了解了,这里就做过多的介绍了。但是值得注意的是,Canvas 中 font 属性的的默认值为 10px sans-serif。从上边我们介绍 measureText 方法时也提到,如果 font 属性一旦被定义,之后所有的文本都会按这个属性进行绘制,直到 font 属性被重新定义其他属性值为止。
textAlign属性
textAlign 属性用于处理文本在水平方向上的对齐方式。
语法:
js
ctx.textAlign = "属性值";
textAlign 具体有哪些属性值呢,我将这些属性值归纳在一个表格中:
属性值 | 说明 | 对齐方式 |
---|---|---|
left | 文本在指定横坐标开始 | 左对齐 |
right | 文本在指定横坐标结束 | 右对齐 |
start | 类似于left | 阅读开始方向对齐 |
end | 类似于right | 阅读结束方向对齐 |
center | 文本的中点在指定横坐标 | 居中对齐 |
光看这个表格,可能还是有些不够直观,我们来看看代码和结果就比较好理解了。
js
<template>
<canvas ref="cnv" width="710" height="400" style="border: 1px dashed gray"></canvas>
</template>
<script setup>
import {ref, onMounted} from "vue";
const cnv = ref();
const drawFont = () => {
const ctx = cnv.value.getContext('2d');
const canvasWidth = cnv.value.width;
const canvasHeight = cnv.value.height;
const canvasMidWidth = canvasWidth / 2;
ctx.beginPath();
ctx.moveTo(canvasMidWidth, 0);
ctx.lineTo(canvasMidWidth, canvasHeight);
ctx.setLineDash([5, 5]);
ctx.stroke();
ctx.fillStyle = "hotpink";
ctx.font = "bold 30px ping-fang";
ctx.textAlign = "left";
ctx.fillText("left", canvasMidWidth, 70);
ctx.textAlign = "start";
ctx.fillText("start", canvasMidWidth, 140);
ctx.textAlign = "right";
ctx.fillText("right", canvasMidWidth, 210);
ctx.textAlign = "end";
ctx.fillText("end", canvasMidWidth, 280);
ctx.textAlign = "center";
ctx.fillText("center", canvasMidWidth, 350);
}
onMounted(() => {
drawFont();
});
</script>
应用上面的代码,我们可以绘制出这样一个文本对应的不同的 textAlign 属性,就会呈现在水平方向上不同的对齐方式,这个图应该比较直观了吧。针对于 start 与 end 这两个属性值,我觉得还是有必要提一下,虽然我们很少用到。我们现在的阅读与书写习惯都是从左往右进行,然而我们在一些书法作品中往往是从右往左进行阅读与书写,这是源于古人书写竹简时需要右手执笔,左手展开,为了便于书写所养成的习俗。也就是如果阅读方向是从左到右,那么 start,end 与 left,right 是一样的,如果阅读方向是从右到左,那么 start 与 left 相反, end 与 right 同理。
textBaseline 属性
在 Canvas 中,textBaseline 属性定义垂直方向上的对齐方式。
语法:
js
ctx.textBaseline = "属性值";
我将 textBaseline 常用属性值归纳在一个表格中:
属性值 | 说明 |
---|---|
alphabetic | 文本基线是普通英文字母的基线 |
top | 文本基线是 em 框的顶端 |
middle | 文本基线是 em 框的中心 |
bottom | 文本基线是 em 框的底端 |
我们只例举了这几个常用属性,当然还有其他的属性如:hanging,ideographic 等属性值,因为用得很少我们就不做过多的了解了,感兴趣的可以自行学习一下。
我们来看看这些属性值具体应该怎样来使用呢?
js
<template>
<canvas ref="cnv" width="710" height="400" style="border: 1px dashed gray"></canvas>
</template>
<script setup>
import {ref, onMounted} from "vue";
const cnv = ref();
const drawFont = () => {
const ctx = cnv.value.getContext('2d');
const canvasWidth = cnv.value.width;
const canvasHeight = cnv.value.height;
const canvasMidHeight = canvasHeight / 2;
ctx.beginPath();
ctx.moveTo(0, canvasMidHeight);
ctx.lineTo(canvasWidth, canvasMidHeight);
ctx.setLineDash([5, 5]);
ctx.stroke();
ctx.fillStyle = "hotpink";
ctx.font = "bold 30px ping-fang";
ctx.textBaseline = "alphabetic";
ctx.fillText("alphabetic", 70, canvasMidHeight);
ctx.textBaseline = "top";
ctx.fillText("top", 280, canvasMidHeight);
ctx.textBaseline = "middle";
ctx.fillText("middle", 400, canvasMidHeight);
ctx.textBaseline = "bottom";
ctx.fillText("bottom", 560, canvasMidHeight);
}
onMounted(() => {
drawFont();
});
</script>
可能光这么看代码与表格的介绍不是很直观,再结合这绘制结果,来看一下可能会更好理解一些。
前面我们也介绍了英文基线以及 em 框,结合着表格里边的说明,我们可以看到,top 属性值使用时,文本的顶端可能不会直接接触我们绘制的定位线,这也就说明,em 框可能不是跟字符实际大小一致的,所以在绘制的时候稍加注意,利用当前所学知识,是可以进行自我调整的。
总结
这一节,我们用3个属性 (font,textAlign,textBaseline),3个方法 (fillText, strokeText, meatrueText) 就把 Canvas 的文本给介绍完了,总体还是比较简单,如果有 CSS 基础的小伙伴,这一节就更简单一些。好了下一节我们将要探索 Canvas 中图片的处理了。
小插曲
最后说一下写这篇文章的一个小插曲,本来之前就已经写好了,结果因为自己的一个失误,导致了数据的丢失,我简单说一下,避免小伙伴踩坑。这篇文章之前我写了 1/3,然后在手机端我把草稿打开了,然后我就在 mac 上编写,等我写完,我说再去手机上看一眼,结果,进去直接就自动保存了之前的1/3,突然我就意识到出问题了,我赶紧找了一下,看有没有办法恢复,没有找到相关的功能,我再转头一想,好像这个数据没法恢复了,实时保存的,估计没有历史备份了,所以我又重新写了一遍。噗噗噗...当时肝到凌晨,差点让我失眠,转头一想,只能再写一遍,再温故一下吧。所以,小伙伴们,不能让两个终端的草稿同时存在,会出风险。