写完了三年 autocomplete,每次都要靠 contenteditable 克隆输入框——今天 OpaqueRange 把这件事彻底变了
写过 autocomplete 搜索建议、输入框高亮、或者 emoji picker 的人,大概都知道一个痛:原生的 <input> 和 <textarea> 里的文字,你是拿不到它的坐标的。getBoundingClientRect() 不支持,选区信息不暴露,想在光标位置弹个气泡?老老实实把整个输入框用 <div contenteditable> 克隆一份,然后在上面做操作。
这个方案一直能用,但代价不小:光标行为要自己管,键盘事件要自己接,无障碍支持要自己写,一不小心就漏了一个场景。这件事今天被 OpaqueRange 彻底变了。
OpaqueRange 是什么
OpaqueRange 是 Chrome 153 引入的一个 AbstractRange 子类,专门用来操作 <input> 和 <textarea> 这类表单控件内部文字。它和普通 Range 的最大区别是:不暴露内部 DOM 结构——startContainer 和 endContainer 返回 null,只给你文字偏移量(offset),所以不会破坏表单控件的封装。
但它支持了你真正需要的操作:
// 获取光标/选区的屏幕坐标
const range = input.createTextRange(startOffset, endOffset);
const rect = range.getBoundingClientRect();
// 结合 CSS Custom Highlight API 高亮匹配文字
CSS.highlights.set("search-match", new Highlight(range));
三行代码,你不用克隆任何 DOM 节点。
三个真实场景,用法全变了
场景一:autocomplete 弹层定位
以前做输入联想,要拿到光标位置,只能用 <div contenteditable> 克隆输入框,在克隆节点上调 getBoundingClientRect()。OpaqueRange 让你直接在原生 <input> 上拿坐标,弹层定位精度一致,还不会丢原生的 IME 组合输入支持。
场景二:搜索结果高亮
搜索框下面列出匹配内容并高亮,用 CSS Custom Highlight API 配合普通 Range 早就行得通——但前提是高亮区域在普通 DOM 里。OpaqueRange 把这个能力延伸到了表单控件内部:用户打字的每个瞬间,选区坐标实时更新,高亮跟着走,没有克隆节点的延迟。
场景三:语法/拼写检查 UI
浏览器原生的拼写检查用 ::spelling-error / ::grammar-error 渲染,但你想在输入框里画一个自定义的错误下划线样式(比如红色波浪线而不是默认小红块),需要先知道错误范围在哪。OpaqueRange + getClientRects() 让你拿到每个错误字符的精确几何信息,直接画覆盖层。
实际用起来什么样
Microsoft Edge 官方提供了一个完整的 Demo 页面,演示了四个场景:
- Caret popup positioning:在 <input> 或 <textarea> 光标处弹出 emoji picker,坐标直接来自 OpaqueRange.getBoundingClientRect()
- Search highlighting:输入搜索词,实时在 textarea 里用 CSS Custom Highlight API 高亮所有匹配
- Live range tracking:高亮”world”这个词,然后往前打字,高亮范围自动跟着更新
- Disconnect behavior:调用 range.disconnect() 断开连接,范围自动归零,不再接收更新
从 Demo 来看,光标处弹层的定位精度和原生输入框完全一致,不存在克隆方案里滚动时弹层漂移的问题。
现在的兼容性是什么样的
OpaqueRange 经历了两个阶段:
- Chrome 148–150:Origin Trial 阶段,需要申请 Token 启用
- Chrome 153(2026年8月26日稳定版):默认启用
Firefox 和 Safari 目前还没有默认支持,但 Mozilla 和 WebKit 都已在各自 standards-positions 库里标记了 “Positive” 和 “Support”,说明标准方向上已经认可。生产环境可以先做特性检测渐进增强:
if ("createTextRange" in HTMLInputElement.prototype) {
// 使用 OpaqueRange 方案
} else {
// 回退到 contenteditable 克隆方案
}
怎么迁移已有的克隆方案
如果你现在用的是 <div contenteditable> 克隆方案,迁移路径分三步:
第一步:替换坐标获取方式
原来在克隆节点上调用 range.getBoundingClientRect(),改成在原 <input> 上用 input.createTextRange(offsetStart, offsetEnd).getBoundingClientRect()。
第二步:统一选区同步
克隆方案里需要手动把用户输入同步到克隆节点,再用 selectionchange 事件把选区同步回来。OpaqueRange 是 live 的——它自己会跟着用户输入更新,不需要额外同步逻辑。
第三步:保留 contenteditable 回退
Safari/Firefox 还不支持 OpaqueRange,建议用 @supports 或者 typeof HTMLInputElement.prototype.createTextRange !== “function” 做检测,不支持时保留原有克隆方案。
下一步
如果你正在维护一个编辑器类应用、搜索组件、或者任何在输入框上叠加了 UI 的功能,现在可以打开 Chrome 153,在官方 Demo(aka.ms/opaque-range)上试试这个 API 能做到什么。然后检查你的代码库里有没有 <div contenteditable> 克隆输入框的实现——那大概就是 OpaqueRange 的下一个替代目标。
评论区
登录后可评论。