range对象需显式设置起止点才有效,直接调用tostring()或extractcontents()会返回空或报错;selectnode()可完整选中节点,setstart()/setend()支持精细控制;tostring()仅返回纯文本,extractcontents()剪切并返回documentfragment,clonecontents()复制不移除,deletecontents()仅删除;用户选区须用getselection().getrangeat(0)获取;startoffset/endoffset在text节点为字符索引、element节点为子节点序号。

Range API 的基本创建和范围设定
直接用 document.createRange() 创建 Range 对象,它默认不绑定任何内容;必须显式调用 selectNode()、setStart() 或 setEnd() 才能定义有效范围。常见错误是创建后没设起点终点就调用 toString() 或 extractContents(),结果返回空字符串或抛出 InvalidStateError。
最稳妥的起步方式是先选中一个已存在的 DOM 节点:
const range = document.createRange();
const node = document.getElementById('content');
range.selectNode(node); // 完整包含该节点及其子树
// 或更精细地控制:
range.setStart(node.firstChild, 0);
range.setEnd(node.lastChild, node.lastChild.textContent.length);
提取文本内容时要注意的边界行为
range.toString() 只返回纯文本,会丢弃所有标签和格式;而 range.extractContents() 返回的是 DocumentFragment,保留结构但会从原 DOM 中移除对应节点——这点极易被忽略,导致页面内容意外消失。
若只是读取,优先用 toString();若要复制或移动,才用 extractContents() 或 cloneContents():
-
extractContents():剪切,原位置清空 -
cloneContents():复制,原位置不变 -
deleteContents():删除,不返回任何内容
注意:如果范围跨多个块级元素(比如从 <p></p> 开头到 <div> 结尾),<code>toString() 仍能拼出连续文本,但 extractContents() 返回的 DocumentFragment 会保留原始嵌套结构。
处理光标位置与用户选区的实际适配
用户手动选中的内容由 getSelection() 获取,不是直接 new Range;必须从选区中取出第一个 Range(多段选区较罕见):
文章转信息图。将文章/笔记转化为手机可读的 HTML 信息图,自动匹配视觉风格。触发场景:文章转图、笔记转图、信息图、转小红书图、做张图、可视化这篇文章、文生图。
const selection = window.getSelection();
if (selection.rangeCount > 0) {
const range = selection.getRangeAt(0); // ✅ 正确获取当前高亮范围
console.log(range.toString());
}
常见陷阱:
- 在 iframe 或 Shadow DOM 中,
getSelection()必须在对应上下文调用,不能只依赖顶层window - 移动端触发 selection 可能延迟,建议加
setTimeout或监听selectionchange事件再读取 -
range.commonAncestorContainer是推断选区“所属区域”的关键,可用于判断是否在编辑器容器内
Range 跨文本节点时的 startOffset/endOffset 含义
startOffset 和 endOffset 的单位取决于 startContainer 类型:如果是 Text 节点,表示字符索引;如果是 Element 节点,表示子节点序号(从 0 开始)。混用会导致范围错位。
例如:
<div>abc<span>def</span>ghi</div>
若 range.startContainer === div 且 startOffset === 1,则起点在 <span></span> 这个子节点处(即 abc 之后、span 开始前);若 startContainer 是第一个 Text 节点,则 startOffset === 1 指的是字符 'b' 的位置。
调试时可打印:
console.log({
container: range.startContainer.nodeType === 3 ? 'Text' : 'Element',
offset: range.startOffset
});
复杂文档片段操作里,这个差异是多数 Range 行为异常的根源——尤其当动态插入节点后未更新 range,startOffset 可能越界或指向错误位置。










