range.surroundcontents() 报错因选区必须完全落在单个文本节点内,跨标签、换行或含特殊字符即失败;应先校验节点一致性,否则改用 extractcontents()+insertnode()。

为什么直接用 range.surroundContents() 会报错 InvalidStateError
因为这个方法只接受「完全落在单个文本节点内」的选区。只要用户拖选跨了 <span></span>、<em></em> 或换行标签,range.surroundContents() 就立刻失败——不是代码写错了,是 DOM 结构不满足前提。
常见触发场景包括:选中“hello world”,或从段落开头拖到结尾(中间有多个子节点);甚至纯文本里含零宽空格、emoji 组合符,都可能导致 range.startContainer 和 range.endContainer 不是同一个 Text 节点。
- 务必先校验:
if (range.startContainer === range.endContainer && range.startContainer.nodeType === 3) - 否则改用
range.extractContents()拿出文档片段,再包裹新span,最后range.insertNode() - 注意:
extractContents()会移除原节点,若需保留结构,应先cloneContents()再操作
如何安全地给高亮加 data-highlight-id 并支持清除
不带标识的高亮等于埋雷——下次想取消某次标注,只能靠 class 硬匹配,一碰上用户自己写的 highlight 类就冲突;更别说多人协作时重复高亮无法区分归属。
正确做法是在插入时生成唯一 ID,并绑定到包裹元素上:
const span = document.createElement('span');
span.className = 'user-highlight';
span.dataset.highlightId = 'hl-' + Date.now() + '-' + Math.random().toString(36).substr(2, 5);
span.style.backgroundColor = '#ffeb3b';
清除时按 ID 精确查找并还原:
- 遍历所有
span[data-highlight-id],用node.replaceWith(...node.childNodes)拆出原始内容 - 别用
innerHTML = node.innerHTML,会丢失事件监听器和 input 光标位置 - 如果高亮区域在
contenteditable区域内,清除后记得调用selection.removeAllRanges()防止残留光标错位
window.getSelection() 返回空或 rangeCount === 0 的真实原因
不是用户没选,而是当前上下文根本不允许 Selection 对象被创建——这在富文本编辑器里高频出现。
典型原因包括:
- 编辑器容器设置了
contenteditable="false",或父级有pointer-events: none - 页面焦点不在当前 tab,或用户刚从地址栏切回来(iOS Safari 尤其敏感)
- 在 iframe 里操作,但主页面与 iframe 不同源,浏览器策略直接禁用 selection 访问
- 用户长按唤起系统菜单(Android)、或双指缩放过程中,selection 被临时冻结
所以每次调用前必须双保险:
const sel = window.getSelection(); if (!sel || sel.rangeCount === 0 || sel.isCollapsed) return;
移动端 iOS Safari 的两个隐藏坑
iOS Safari 对 Selection API 的限制比桌面端严格得多,尤其在非 contenteditable 区域里——即使用户明显长按选中了文字,getSelection().rangeCount 仍可能为 0。
这不是 bug,是 Apple 的主动策略:防止网页脚本劫持用户选中行为。应对方式只有两种:
- 对关键区域显式加
contenteditable="true"(哪怕只是readonly),确保 selection 可读 - 降级使用
document.addEventListener('selectionchange', ...),但它在 iOS 上触发极不可靠,建议仅作 fallback - 视觉对齐别依赖
range.getClientRects(),它在软键盘弹出后坐标常偏移;改用range.getBoundingClientRect()并监听scroll和resize重算
真正难处理的,是那些既不能改 HTML 结构(比如嵌入第三方编辑器 SDK),又必须支持 iOS 高亮的场景——这时候得接受部分功能妥协,或者引入 rangy 这类兼容层库兜底。
大量免费API接口:立即使用
涵盖生活服务API、金融科技API、企业工商API、等相关的API接口服务。免费API接口可安全、合规地连接上下游,为数据API应用能力赋能!











