HTML选区(Selection)仅用于读取用户选中范围,不可直接修改内容;必须转为Range对象才能执行删除、插入等DOM操作,且surroundContents()有严格限制,易抛错。

HTML选区(Selection)本身不操作文本,它只是“告诉你用户划了哪一块”;真要改内容,必须转成 Range 才能动手——跳过这步,所有替换、包裹、高亮都会失效。
getSelection() 返回的是只读状态,不是可编辑对象
window.getSelection() 拿到的是浏览器维护的当前选中状态快照,它不能直接 .textContent = 'xxx',也不能调用 .replace()。它的作用仅限于读取:比如判断是否选中(rangeCount > 0)、获取起止节点(anchorNode/focusNode)和偏移(anchorOffset/focusOffset)。
常见错误现象:
- 调用
selection.toString()得到字符串后直接赋值给某个元素,结果 DOM 完全没变 - 误以为
selection.anchorNode.textContent改了就能更新选区内容,其实只是改了那个节点的全部文本,跟选区无关
正确做法是立刻检查 rangeCount,再取 getRangeAt(0):
立即学习“前端免费学习笔记(深入)”;
const sel = window.getSelection(); if (sel.rangeCount === 0) return; // 没选中,退出 const range = sel.getRangeAt(0); // ✅ 这才是可操作实体
用 Range.deleteContents() + insertNode() 替换选中文本
想把“hello”替换成“world”,不能靠字符串拼接再 innerHTML;必须走 DOM 节点级操作,否则会破坏结构、丢失事件绑定或样式继承。
关键点:
使用 PptxGenJS 和 Python 解析,将结构化的 HTML/CSS 幻灯片内容转换为原生、可编辑的 PowerPoint(.pptx)演示文稿。
-
range.deleteContents()会真正从 DOM 中移除选中部分,包括跨多个Text节点或嵌套Element的情况 -
range.insertNode(node)插入的是完整节点,支持document.createElement('span')或document.createTextNode('text') - 如果插入 HTML 字符串,必须先解析为节点:
range.insertNode(document.createElement('div').innerHTML = '...')不行,得用DOMParser或临时div+firstChild
示例:用 span 包裹选中文本并加背景色
const sel = window.getSelection();
if (!sel.rangeCount) return;
const range = sel.getRangeAt(0);
const span = document.createElement('span');
span.style.backgroundColor = '#ffeb3b';
span.textContent = range.toString(); // 注意:这里只是取值,不是回写
range.deleteContents();
range.insertNode(span);
surroundContents() 看似方便,但极易崩溃
range.surroundContents() 的设计初衷是“把选中内容包进一个新节点”,但它有极严格的前置条件:选区必须完全落在**单个 Text 节点内**,且不能跨 Element 边界。一旦 anchorNode 和 focusNode 不同,或 offset 落在 Element 上(比如 anchorNode 是 <p>,anchorOffset 是子节点序号),调用就直接抛 InvalidStateError。
容易踩的坑:
- 用户反向拖选(从右往左),
anchorOffset > focusOffset,surroundContents拒绝处理 - 选中文字横跨
<strong>hi</strong> world,起始在strong内、结束在后面Text节点,必然失败 - 现代框架(React/Vue)下,手动操作 DOM 可能与虚拟 DOM 冲突,导致下次渲染异常
替代方案始终更稳:先 deleteContents(),再用 insertNode() 插入包装节点,哪怕多写几行。
移动端和 iframe 里 selectionchange 不可靠
document.addEventListener('selectionchange', handler) 在桌面 Chrome/Firefox 基本可用,但在 Safari(尤其 iOS)和 iframe 场景下经常掉帧或不触发。
真实使用建议:
- 不要依赖它做实时标注菜单弹出,改用
mouseup+setTimeout(() => { if (getSelection().rangeCount) {...} }, 0)主动检查 - 在 iframe 里监听,必须在 iframe 的
contentDocument上绑,父页面监听不到 - 移动端软键盘弹起时,selection 可能被重置为 collapsed,
rangeCount突然变 0,需加兜底逻辑
最常被忽略的一点:selection 是全局状态,但不是响应式数据——它不会自动通知你变化,也没有 getter/setter,所有操作都得靠主动轮询或事件试探。


















