在网页里做文本选中、批注、高亮或轻量编辑时,很多问题都卡在“看得见的选区”和“代码里的操作范围”不是一回事。本文把 window.getSelection() 与 document.createRange() 分开讲清,再串起来说明它们如何配合,让你能判断当前选区、主动指定范围,并把删除、插入、包裹等操作准确落到目标文本上。
先分清:Selection 和 Range 各管什么
window.getSelection() 获取的是浏览器当前维护的全局选区,重点在“用户现在选了哪里”。而 document.createRange() 创建的是一个可编程范围,重点在“代码准备操作哪一段”。
实际开发里,这两个 API 往往需要一起用:
- 先通过
window.getSelection()读取用户当前选中的内容 - 再通过
document.createRange()创建或调整精确边界 - 最后把 Range 交回 Selection,才能在页面上真正显示成选中状态
换句话说,Selection 更像页面当前的“选中结果”,Range 则是你可以拿来读、写、高亮、格式化文本的“操作坐标”。
如何获取并判断当前选区
当用户在页面中拖选文本后,浏览器会自动维护一个 Selection 对象。最常见的入口就是:
const sel = window.getSelection()
拿到它之后,可以先做几个基础判断。
读取选中的文本
如果只关心用户选中了什么内容,最直接的方法是:
sel.toString()
这个调用返回的是选中的纯文本,适合做复制、提示或快速判断。
判断有没有真正选中内容
有时页面里看起来有光标,但其实并没有有效选区。这时可以检查:

sel.rangeCount === 0:表示没有任何 Rangesel.isCollapsed为true:表示起点和终点重合,当前只是光标状态
这两个条件很常用于编辑器、评论框和批注工具里,避免在空选区上误执行格式化或删除动作。
理解选区的起点和终点
Selection 还保存了选区两端的信息:

- 起点:
sel.anchorNode/anchorOffset - 终点:
sel.focusNode/focusOffset
这里要注意,起点和终点可能位于不同节点中,尤其是在跨标签选中文本时更常见。也正因为如此,直接依赖字符串处理往往不够稳,通常还是要交给 Range 按节点边界来做精确操作。
如何创建并精确定位一个 Range
如果说 Selection 反映的是当前状态,那么 Range 更像一把可编程的“文本尺子”。它不依赖用户手动拖选,完全可以由脚本主动创建:
const range = document.createRange()
在单个文本节点里设置范围
当目标内容位于纯文本节点内部时,可以直接通过偏移量设定起止位置。比如:
range.setStart(textNode, 3)
range.setEnd(textNode, 8)
如果节点内容是 Hello world,那么这段范围圈出的就是 lo wor。这种写法适合做字词级高亮、局部替换或精确插入。
在多个子节点之间设置范围
如果目标跨越多个子节点,就可以把元素节点本身作为参考。比如结构是:
<p>Hi <em>there</em>.</p>
这时可以这样设定:
range.setStart(p, 0)
range.setEnd(p, 2)
range.setStart(p, 0) 表示从 p 的第 0 个子节点开始,range.setEnd(p, 2) 表示到第 2 个子节点之前结束。它适合处理跨标签选区,不需要把内容先拍平成一段字符串再回填到 DOM。
几个常用快捷方法
range.selectNodeContents(el):选中整个元素内部内容range.collapse(false):把范围折叠到末尾,变成一个光标位置
前者常用于“一键选中整段内容”,后者常用于把光标移动到编辑器末尾或指定位置。
为什么创建了 Range 还要交给 Selection
很多人第一次接触这组 API 时会疑惑:Range 明明已经定义好了边界,为什么页面上看不到蓝色高亮?原因是 Range 只是代码中的范围对象,要让浏览器把它显示成当前选区,还需要交给 Selection。
典型流程如下:
sel.removeAllRanges()
sel.addRange(range)
sel.removeAllRanges()先清空现有选区sel.addRange(range)再把刚创建的 Range 加进去
这样浏览器才会把这段范围显示为真正的选中状态。
把光标移动到指定位置
如果你的目标不是高亮文本,而是把光标挪到某个位置,比如编辑器末尾,也可以先创建 Range,再折叠后加入 Selection。关键点不在“选中多长”,而在“折叠后把当前位置交给浏览器”。
关于多选区的限制
理论上 Selection 可以包含多个 Range,但在多数浏览器里,可见的多 Range 高亮支持有限,尤其不适合当成稳定能力来依赖。实际项目中,通常还是按“一次只处理一个可见选区”来设计更稳妥。
拿到 Range 后,能直接做哪些操作
真正进入富文本编辑、批注或内容增强场景后,Range 的价值就体现出来了。只要边界设得准,后续操作就能尽量避免误伤周边 DOM 结构。
复制选中片段
range.cloneContents()
这个方法会复制选中部分的 DOM 片段,而且会保留其中的标签结构,不只是纯文本。适合做富文本复制、片段预览或二次加工。
删除选中内容
range.deleteContents()
执行后,选中的内容会被删除,DOM 会自动重排。相比手工查找节点再逐个删,这种基于范围边界的方式更适合编辑型交互。
包裹选中的内容
range.surroundContents(document.createElement('strong'))
这会把选中文本包进一个 <strong> 元素中,是实现加粗、强调或特定标注的常见手法。
在选区插入新节点
range.insertNode(node)
如果你需要插入链接、图标或其他 DOM 节点,可以直接使用这个方法。只要 Range 的起点和终点定义正确,插入动作就会准确落在目标位置,而不会波及周围无关内容。
总的来说,Selection 负责承接用户当前的选中状态,Range 负责给脚本提供精确边界。把这层关系理顺后,网页里的文本读取、高亮、格式化和编辑动作就会清晰很多,也更容易做出稳定的交互实现。









