canvas 选区同步靠了七八年的土办法,今天 Chrome 157 彻底原生化了
每次在 canvas 上画完文本想高亮选区,都要偷偷在页面里塞一个隐藏的 <div> 做测量——这不是传闻,是 Flutter Web、Canvas 编辑器、创意编程框架共同踩了七八年的坑。Chrome 157 今天把这个 DOM 同步的土办法彻底原生化了。
旧世界:canvas 选区靠 DOM 同步
canvas 上的 measureText() 只返回一个 width。如果想知道用户点击了哪段文字、选区覆盖了哪些像素,标准做法是:
// 在 canvas 旁边放一个透明 DOM 节点
const ghost = document.createElement('div');
ghost.style.cssText = 'position:absolute;visibility:hidden;white-space:nowrap;';
document.body.appendChild(ghost);
// 每次 canvas 内容变化,同步字体信息
function syncToGhost(text, font) {
ghost.style.font = font;
ghost.textContent = text;
return ghost.getBoundingClientRect();
}
// 靠 selectionStart + getBoundingClientRect() 估算选区
const sel = window.getSelection().getRangeAt(0);
const rect = ghost.getBoundingClientRect(); // 每次都要触发 layout
这个模式有三个根本问题:
- DOM 和 canvas 状态必须严格同步 — 字体、大小、换行任何一处不一致,测量就偏差
- 性能代价高 — 每个字符的选区都要触发一次 layout 计算
- RTL / 混合方向文本基本无解 — DOM 的
getSelection()对 canvas 内容是盲的
Chrome 157 的 4 个新 API
Chrome 157 为 TextMetrics 增加了四个新方法,全部在 canvas 内部完成,不再需要 DOM 同步:
const canvas = document.getElementById('editor');
const ctx = canvas.getContext('2d');
ctx.font = '18px "Noto Sans SC", sans-serif';
const metrics = ctx.measureText('Hello 你好 World');
// ① 根据像素偏移量找到字符索引(光标定位用)
const idx = metrics.getIndexFromOffset(canvas, 85); // x=85px 处是哪个字符
// ② 拿到一段文字在屏幕上对应的所有矩形(选区高亮用)
const rects = metrics.getSelectionRects(0, 12); // 0 到 12 字符的选区矩形
// ③ 拿到子字符串的精确边界框(碰撞检测用)
const box = metrics.getActualBoundingBox(0, 5); // "Hello" 的边界盒
// ④ 按书写方向分组建簇(逐字动画用)
const clusters = metrics.getTextClusters();
clusters.forEach(cluster => {
drawCharacter(ctx, cluster.glyphs, cluster.advance);
});
这四个 API 的能力:
| API | 解决了什么问题 | 之前只能靠什么 |
|---|---|---|
getIndexFromOffset |
知道用户点击了第几个字符 | selectionStart + DOM |
getSelectionRects |
绘制正确的选区高亮矩形 | window.getSelection() + 隐藏 div |
getActualBoundingBox |
精确知道某段文字的像素边界 | DOM getBoundingClientRect() |
getTextClusters |
逐字符/逐词独立渲染动画 | 手动切分字符串 + 手动算位 |
Flutter Web 已经靠它跑了近一年
这个功能不是实验室玩具。Flutter Web 在 2025 年初就接入了 Origin Trial(Chrome 144–152,历时约 9 个月),用 getTextClusters() 和 getSelectionRects() 重写了 WebParagraph 的文本布局引擎,在正式发布前已经有大量生产流量验证。
Canvas 创意编程也有成熟案例——文字沿路径编辑器(text-on-a-path)和逐字爆炸动画,之前必须用 DOM 节点同步才能实现的效果,现在一行 canvas API 搞定。
兼容性现状(截至 2026 年 10 月):
- ✅ Chrome/Chromium:157+(2026 年 10 月 7 日稳定版)
- ⚠️ Firefox:暂无表态(Mozilla standards-positions Issue #1144 未给出信号)
- ⚠️ Safari/WebKit:持保留意见(偏好
TextLine这样的行级抽象,而非扩展TextMetrics)
检测与降级
function canvasTextEditingSupported() {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
const metrics = ctx.measureText('test');
return (
typeof metrics.getIndexFromOffset === 'function' &&
typeof metrics.getSelectionRects === 'function'
);
}
if (canvasTextEditingSupported()) {
// 使用新 API
} else {
// 降级方案:保留 ghost div 逻辑
initGhostDivFallback();
}
接下来可以做的事
- 打开 Chrome 157,用 Igalia 的在线 Demo 实际感受 canvas 内直接选中文本
- 检查你的 canvas 编辑器是否还在用
ghost div模式——是的话,现在有原生替代了 - 关注 Firefox 的 standards-positions 进展;若 Safari 也跟进,canvas 文本编辑的跨浏览器兼容成本会大幅下降
评论区
0 条评论
登录后可评论。