写完了三年 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 页面,演示了四个场景:

  1. Caret popup positioning:在 <input> 或 <textarea> 光标处弹出 emoji picker,坐标直接来自 OpaqueRange.getBoundingClientRect()
  2. Search highlighting:输入搜索词,实时在 textarea 里用 CSS Custom Highlight API 高亮所有匹配
  3. Live range tracking:高亮”world”这个词,然后往前打字,高亮范围自动跟着更新
  4. 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 的下一个替代目标。

评论区

0 条评论

登录后可评论。

阿柯·前端架构 10 阅读