HTML5 measureText()使用自定义字体时首次文本宽度计算错误
问题根本原因
问题由两个核心问题共同导致:
- 代码执行顺序错误:Canvas 2D上下文的属性状态是持久保留的,你当前的代码先执行文本宽度测量,后设置字体属性:
- 首次调用绘制函数时,上下文默认字体为浏览器初始值(通常为
10px sans-serif),此时测量得到的是默认字体下的文本宽度 - 测量完成后你才设置
12px fff自定义字体,后续文本渲染使用的是自定义字体,两种字体的字符宽度存在差异,自然出现位置偏移 - 首次绘制完成后,上下文的font属性已经被修改为自定义字体,后续再次调用函数时,测量和渲染都使用同一个自定义字体,因此居中正常
- 当你注释掉字体设置行后,上下文始终使用默认字体,测量和渲染的字体一致,因此无论第几次点击都能居中,这也直接验证了顺序错误的影响
- 首次调用绘制函数时,上下文默认字体为浏览器初始值(通常为
- 字体预加载方案不可靠:你使用的隐藏div触发字体加载的方案存在竞态问题,DOM节点触发字体加载不代表Canvas渲染上下文能立刻获取到可用的字体资源,即使修正代码顺序,在字体实际下载完成前,Canvas仍会回退到默认字体进行测量和渲染。
修复方案
- 第一步:调整代码执行顺序,必须先设置上下文字体属性,再执行文本测量和绘制操作,修正后的核心绘制代码如下:
drawPlayerCard = (card, player) => { let ctx = this.leftUserContext; ctx.drawImage(card.template, card.position.x, card.position.y); // 先设置字体样式,再进行测量和绘制 ctx.font = "12px fff"; const nameWidth = ctx.measureText(player.name).width; ctx.fillText(player.name, ((card.template.width - nameWidth) / 2) + card.position.x, (202 + card.position.y)); }
- 第二步:替换不可靠的隐藏div预加载方案,使用浏览器原生Font Loading API确认字体加载完成后,再开放游戏交互逻辑,从根源消除竞态问题:
// 页面初始化阶段执行字体加载校验 document.fonts.load('12px fff').then(() => { // 字体加载完成后再绑定球员点击事件、启动游戏交互 // 所有涉及Canvas文本绘制的逻辑都放在该回调触发后执行 }).catch(() => { // 字体加载失败时的回退逻辑,可选择降级使用系统默认字体 console.warn('自定义字体加载失败,将使用系统默认字体渲染'); });
- 可选兼容处理:如果需要适配更老旧的浏览器,可以在字体加载完成前给侧边球员卡区域增加加载遮罩,避免用户提前点击触发未就绪的绘制逻辑。
内容的提问来源于stack exchange,提问作者Davide Vitali
相关产品推荐
相关产品推荐

