0%

小程序真机下 canvas metrics 无法获取字体高度的问题

小程序真机下会出现canvas metrics 无法获取字体高度的问题,模拟器下表现正常。

背景描述

在一个项目下需要在canvas中对一行文本垂直居中展示,以下是一个示例效果:

文本垂直居中展示

在模拟器上的表现很正常,但是到了真机上后文本位置出现严重的偏差。验证了iOS设备和Android设备都无法按预期做到垂直居中展示。

初步分析

通过输出metrics返回的值,可以看到模拟器上是可以获取到widthheight的,而在真机上只能获取到有效的width,而height的值为0。

对比效果

所以通过下述代码进行文本绘制时就出现了异常:

1
2
3
4
const metrics = context.measureText('中');
const fontWidth = metrics.width;
const fontHeight = metrics.fontBoundingBoxAscent;
context.fillText('中', width / 2 - fontWidth / 2, height / 2 + fontHeight / 2);

在真机上验证,metrics.fontBoundingBoxAscent的返回值是undefined,那么height / 2 + fontHeight / 2的计算结果就是NaN,因此在绘制的时候,就相当于在y轴上绘制在坐标为0的位置。
在绘制的时候,为了便于计算居中方便,设置了context.textBaseline = "bottom";,因此如果在y轴上坐标为0的文字就完全不可见了。

关于textBaseline的详细说明可以详见:MDNw3schools

metrics兼容性

印象中,metrics在手机端浏览器的兼容性还是蛮好的,包括 Safari on iOS 和 WebView Android,如下图所示:

浏览器兼容性

注:这里只截取了使用到的两个属性metrics.widthmetrics.fontBoundingBoxAscent

从截图上可以看出,metrics.width在很早很早就支持了,而metrics.fontBoundingBoxAscent的支持在各个平台上支持的时间都比较晚。

我目前使用的iOS手机上的safari版本是15.4,是完全支持metrics.fontBoundingBoxAscent这个属性的,但是在小程序中仍然也无法获取到字体的高度。

这就完全超出我的认知和理解了,无奈之下只能联系小程序的开发者。

官方解答

以下解答的时间点:2022-05-26

小程序最低是按 chrome 66, safari 10 支持的
只能是做这种的最低适配。我们只能保证运行环境不会低于这个版本
无法保持一致的,本身运行环境就不同
模拟器基于node可以还可以用最新的js语法,但真机上就不一定可以

降级处理

既然目前(2022-05-26)小程序上无法获取到文本的高度,那么如何进行垂直居中计算呢?

以下是在 mac chrome 下测试 Helvetica 和 Arial 字体,从 10px 到 100px 分别计算 高度和宽度的比值。

字体 10px 20px 30px 40px 50px 60px 70px 80px 90px 100px
Helvetica 1.18 1.08 1.11 1.1 1.12 1.1 1.11 1.1 1.1 1.1
Arial 1.12 1.12 1.12 1.12 1.12 1.12 1.12 1.12 1.12 1.13

以下是在 mac safari 下测试 Helvetica 和 Arial 字体,从 10px 到 100px 分别计算 高度和宽度的比值。

字体 10px 20px 30px 40px 50px 60px 70px 80px 90px 100px
Helvetica 1.2 1.15 1.16 1.15 1.16 1.15 1.15 1.15 1.15 1.15
Arial 1.1 1.1 1.1 1.1 1.12 1.11 1.11 1.11 1.11 1.12

以下是在 iOS safari 下测试 Helvetica 和 Arial 字体,从 10px 到 100px 分别计算 高度和宽度的比值。

字体 10px 20px 30px 40px 50px 60px 70px 80px 90px 100px
Helvetica 1.3 1.25 1.2 1.2 1.18 1.18 1.17 1.17 1.16 1.17
Arial 1.3 1.2 1.16 1.15 1.14 1.13 1.12 1.12 1.13 1.13

详细测试代码详见:codepen

因此,对于无法获取到metrics.fontBoundingBoxAscent属性值的时候,可以使用metrics.width * 1.15做一个降级处理。

修改后的代码如下:

1
2
3
4
5
6
7
8
9
10
11
context.font = '50px Helvetica';
context.textBaseline = "bottom";
const metrics = context.measureText('中');
const fontWidth = metrics.width;
let fontHeight = metrics.fontBoundingBoxAscent;

if (!fontHeight) {
fontHeight = fontWidth * 1.15;
}

context.fillText('中', width / 2 - fontWidth / 2, height / 2 + fontHeight / 2);

最终验证,修改后的代码在小程序上可以“近似”做到垂直居中。

文本近似垂直居中展示