如何实现textarea与canvas fillText的完美对齐?
核心原因
两者文本定位差异主要来自渲染引擎的文本基线规则、度量方式差异以及Canvas的像素缩放处理:
文本基线规则不同
Canvas的fillText默认使用alphabetic基线,你传入的Y坐标是字母"x"底部的位置;而textarea的文本布局,即使设置line-height:1,浏览器仍会基于字体的em框进行对齐,其文本的实际基线位置与Canvas的alphabetic基线存在细微偏移,大字号下会更明显。字体度量的计算逻辑差异
你用metrics.fontBoundingBoxAscent作为Canvas文本的Y坐标,这个值是Canvas引擎计算的字体实际上升高度;但textarea的文本垂直位置由浏览器CSS布局引擎决定,它使用的字体度量标准(如行高、字体内部间距)和Canvas不完全一致,导致两者的文本顶部/基线位置无法完全匹配。Canvas的DPR缩放影响
Canvas的实际尺寸是WIDTH * devicePixelRatio,绘制字体时也放大了DPR倍,但textarea基于CSS像素渲染,虽然逻辑尺寸一致,但Canvas的度量值(如fontBoundingBoxAscent)是缩放后的像素值,直接作为Y坐标会和textarea的CSS像素定位产生偏差。
修复方案
调整Canvas的文本基线与定位
将Canvas的textBaseline设置为top,让fillText的Y坐标对应文本顶部位置,再结合textarea的line-height等于字体大小,即可对齐:
// 在useEffect中添加 context.textBaseline = 'top'; // 调整绘制位置 context.fillText('Hello', 0, 0); context.fillText('World', 0, FONT_SIZE * devicePixelRatio);
统一textarea的布局规则
确保textarea的line-height严格等于字体大小,明确消除隐性布局偏差:
textarea { /* ... 原有样式 */ line-height: 80px; /* 与font-size保持一致 */ }
对齐DPR缩放后的坐标
如果需要保留alphabetic基线,可以通过计算textarea的基线偏移调整Canvas的Y坐标,或者使用actualBoundingBoxAscent替代fontBoundingBoxAscent(后者是字体设计边界,前者是实际文本的渲染边界)。
内容的提问来源于stack exchange,提问作者Saito Asuka

