html selection 是 dom 接口,记录选区的 node 和偏移量而非文本内容;核心操作需通过 range 实现,如 extractcontents() 和 insertnode() 保结构替换文本,避免直接改 textcontent 或依赖已废弃的 execcommand。

HTML 选区(Selection)本身不直接“支持”文本操作,但它提供了精确控制光标位置、获取/替换选中文本、甚至跨节点编辑的底层能力——关键在于你用不用得准、绕不绕得开 DOM 边界和浏览器差异。
为什么 document.getSelection() 拿到的不是纯文本对象?
Selection 是一个 DOM 接口,代表用户在页面中划出的选区,它不持有文本内容,只记录起始/结束的 Node 和偏移量(anchorOffset / focusOffset)。这意味着:
- 选中文字跨
<span></span>和<p></p>时,toString()能返回拼接后的字符串,但无法直接定位到原始字符位置 -
getRangeAt(0)返回的Range才是操作核心——所有插入、删除、包裹都得靠它 - 调用
range.deleteContents()后,DOM 结构可能碎裂(比如把一个文本节点切成两半),后续range.insertNode()容易报错InvalidStateError
想替换选中文本?别直接改 textContent,用 range.extractContents() + range.insertNode()
直接赋值父元素的 textContent 会清空全部子节点(包括事件监听器、内联样式、自定义属性),而基于 Range 的操作能保结构、保行为。典型流程:
使用 PptxGenJS 和 Python 解析,将结构化的 HTML/CSS 幻灯片内容转换为原生、可编辑的 PowerPoint(.pptx)演示文稿。
const selection = document.getSelection();
if (selection.rangeCount > 0) {
const range = selection.getRangeAt(0);
const fragment = range.extractContents(); // 移除原内容,返回 DocumentFragment
const newText = document.createTextNode('替换了');
range.insertNode(newText); // 插入新文本节点
selection.removeAllRanges(); // 清空选区,避免残留高亮
}
- 注意:如果选区跨多个块级元素(如同时选中两个
<div>),<code>extractContents()仍能工作,但插入时需确保目标range有效(建议先collapse(true)再插入) - 若要保留格式(比如只替换文字但不破坏
<strong></strong>包裹),应遍历fragment中的文本节点逐个处理,而非整个替换 - 不返回操作结果,无法判断是否成功(例如当前无选区时静默失败)
- 对非
contenteditable元素无效;且 Chrome 98+ 对非焦点 editable 元素已禁用 - 输出 HTML 不可控(不同浏览器生成的标签不同:
<b></b>vs<strong></strong>) - 2023 年后新项目应优先封装
Range+insertNode/surroundContents,仅把execCommand当作降级 fallback - 执行完
range.insertNode()后没手动恢复选区:用selection.removeAllRanges()+selection.addRange(range)重新聚焦 - 在
input或textarea中混用 DOM 选区 API:它们要用element.selectionStart/selectionEnd,和document.getSelection()完全隔离 - React/Vue 等框架里直接操作 DOM 选区,但状态更新触发了重渲染:务必在
useEffect或nextTick后再读取/设置选区,否则被框架 DOM diff 覆盖
execCommand 已废弃,但有些场景它比 Range 更省事?
是的,比如加粗、插入链接、居中对齐这类富文本操作,document.execCommand('bold') 依然被主流编辑器(如 Slate、Draft.js 底层)用于快速响应用户快捷键。但它有硬伤:
选区消失、光标乱跳?检查这三件事
这是实战中最常打断开发节奏的问题:
真正难的不是拿到选区,而是预判它落在哪个节点、怎么安全拆分文本节点、以及如何在框架生命周期里卡准时机——这些细节不写几十次 Range 操作根本意识不到。










