浏览器原生Selection不支持多重选区,rangeCount恒≤1;需手动监听mouseup、克隆Range并存入数组,操作前用intersectsNode校验有效性,避免DOM变动导致失效。

浏览器原生 Selection 不支持多重选区
直接调用 window.getSelection().getRangeAt(1) 会抛出 IndexSizeError: Failed to execute 'getRangeAt' on 'Selection': The index provided (1) is larger than the maximum bound (0)。因为原生 Selection 对象的 rangeCount 永远 ≤ 1 —— 即使用户按住 Ctrl/Cmd 点击多个位置,也只会保留最后一个操作产生的 Range。所谓“多重选区”是编辑器自己模拟的,不是 DOM 标准能力。
手动维护 storedRanges 数组的关键要点
要实现视觉和逻辑上的多选效果,必须在鼠标事件中主动捕获、克隆并存储 Range:
- 监听
mouseup,检查e.ctrlKey和window.getSelection().rangeCount > 0 - 每次获取后必须调用
range.cloneRange(),否则原始 Range 是活引用,后续 selection 变更会导致它自动失效 - 存入数组如
storedRanges.push(range),不能复用同一个 Range 实例 - 避免用
selection.removeAllRanges()后反复addRange()—— Chrome/Firefox 已弃用该行为,仅 Safari 旧版有残留支持,且第二次调用无效
高亮与操作时 Range 失效的典型表现
DOM 结构一旦变化(比如插入节点、删除内容、格式化),所有已存储的 Range 都可能失效:
-
range.toString()返回空字符串 -
range.getBoundingClientRect()抛出TypeError: Illegal invocation -
range.intersectsNode(node)或range.compareBoundaryPoints()成为唯一可靠判断手段 - 执行
range.deleteContents()后,其他未更新的 stored Range 的startContainer可能已从 DOM 中移除
稳妥做法是:每次操作前先用 range.intersectsNode() 过滤有效 Range;批量操作优先考虑用临时 <mark></mark> 元素包裹,而非直接修改 DOM。
将调研报告、方案对比、数据分析、需求梳理、会议纪要、设计说明、代码评审、学习笔记等任务产物输出为可在浏览器直接打开的单文件 HTML,替代 Markdown 作为最终交付物。Markdown 在信息密度、视觉清晰度和可分享性上已跟不上 AI 产物的丰富度,HTML 能同时承载排版、表格、SVG 等。
立即学习“前端免费学习笔记(深入)”;
移动端与嵌套 Range 的兼容性陷阱
移动端(尤其是 iOS Safari)对 Selection 和 Range 的触发时机极不敏感:
- 点击后常无 selection,需额外
focus()+setSelectionRange()补救(仅适用于input/textarea) - 富文本编辑器中若使用
contenteditable,iOS 上getRangeAt(0)常返回错误偏移,需结合window.getSelection().anchorNode和focusNode手动重建 - 多个 stored Range 存在重叠或嵌套时,
range.commonAncestorContainer可能指向意外节点,导致高亮错位或删除异常
真正难的不是“怎么存多个 Range”,而是“怎么让它们在 DOM 动态变化后还能安全地参与计算和渲染”。这需要持续校验、及时清理、避免强依赖容器节点生命周期。


















