原文链接:https://www.hezebin.com/article/6687947a02a8ac30292a18f8
Range
Range对象表示文档中节点和文本节点的一部分。它允许你创建、操作和检测文档中的任意部分。文档:https://developer.mozilla.org/en-US/docs/Web/API/Range
创建 Range 对象
可以通过 document.createRange()
方法来创建一个新的 Range 对象。
let range = document.createRange();
Range 对象的属性
collapsed
: 一个布尔值,表示 Range
是否为空(即开始和结束位置是否相同)。
commonAncestorContainer
: 包含 Range
开始和结束点的最深公共祖先节点。
startContainer
: Range
的起始节点。
startOffset
: Range
的起始位置相对于起始节点的偏移量。
endContainer
: Range
的结束节点。
endOffset
: Range
的结束位置相对于结束节点的偏移量。
使用 Range 对象
设置 Range 的开始和结束
可以使用 setStart
和 setEnd
方法来设置 Range 的开始和结束位置。
let range = document.createRange();
let startNode = document.getElementById("startNode");
let endNode = document.getElementById("endNode");
range.setStart(startNode, 0);
range.setEnd(endNode, endNode.childNodes.length);
选取节点中的部分内容
let range = document.createRange();
let textNode = document.getElementById("textNode").firstChild;
range.setStart(textNode, 5);
range.setEnd(textNode, 10);
!!! abstract 注意!
如果setStart
时 startNode
是 Text
、Comment
或 CDataSection
类型的节点,则 startOffset
表示从 startNode
开始的字符数量。
对于其他类型的节点,startOffset
表示从 startNode
开始的子节点数量。setEnd
同理。
!!!
Range 对象的操作方法
selectNode
: 选中一个节点及其所有子节点。
range.selectNode(document.getElementById("myElement"));
selectNodeContents
: 选中一个节点的内容。
range.selectNodeContents(document.getElementById("myElement"));
collapse
: 将Range
折叠到起始或结束点。
range.collapse(true); // 折叠到起始点
cloneRange
: 创建一个Range
对象的副本。
let newRange = range.cloneRange();
deleteContents
: 从文档中删除Range
表示的内容。
range.deleteContents();
extractContents
: 从文档中提取Range
表示的内容。
let documentFragment = range.extractContents();
extractContents
方法会从文档中移除 Range 表示的所有内容,并将这些内容作为一个DocumentFragment
返回。这个方法不仅会提取文本内容,还会提取 HTML 元素,且还会包含它们的父节点。
insertNode
: 在Range
的起始位置插入一个节点。
let newNode = document.createElement("span");
range.insertNode(newNode);
surroundContents
: 用一个节点包裹Range
表示的内容。
let wrapper = document.createElement("div");
range.surroundContents(wrapper);
控制 Range 中的内容
可以通过设置 startContainer
、startOffset
、endContainer
和 endOffset
来精确控制 Range 的内容。例如:
假设我们有以下 HTML 结构:
<div id="startNode">Hello <b>world</b></div>
<div id="endNode"><i>This</i> is a <span>test</span></div>
<div id="appendNode"></div>
使用 extractContents()
:
const range = document.createRange()
const startNode = document.getElementById('startNode')?.firstChild
const endNode = document.getElementById('endNode')?.lastChild?.lastChild
if (!startNode || !endNode) {
return
}
range.setStart(startNode, 2) // 偏移 2,以 "Hello " 的第 3 个字符 l 开头
range.setEnd(endNode, 2) // 偏移 2,范围到以 "<span>test</span>" 下 test 的第二个字符 e 结尾
const documentFragment = range.extractContents()
document.getElementById('appendNode')?.appendChild(documentFragment) // 将提取的内容附加到文档的末尾
结果:
<div id="startNode">He</div>
<div id="endNode"><span>st</span></div>
<div id="appendNode">
<div id="startNode">llo <b>world</b></div>
<div id="endNode"><i>This</i> is a <span>te</span></div>
</div>
这里可以看到使用
extractContents
获取到的所选范围的内容包含其父元素
DocumentFragment
DocumentFragment
是一个轻量级的文档对象,可以作为文档的一部分处理。它是一个空的文档片段,没有父级,当你在文档中插入它时,它会被替换为其所有的子节点,而不是它本身。这个特性使 DocumentFragment
成为批量操作 DOM 树的一种有效方法。
DocumentFragment 的特点
- 轻量级:
DocumentFragment
是一个没有父级的节点,其所有操作不会引起 DOM 重排和重绘,直到它被附加到文档中。 - 内存高效:因为它仅存在于内存中,不附加到 DOM 树上,所以操作速度更快,并且性能更高。
- 批量操作:你可以在
DocumentFragment
上进行多次 DOM 操作,然后一次性将它附加到实际的 DOM 树中,减少重排和重绘次数。
使用 DocumentFragment
下面是一些示例,展示如何创建和使用 DocumentFragment
:
示例1:创建和添加子节点
// 创建一个 DocumentFragment
const fragment = document.createDocumentFragment();
// 创建一些新的 DOM 元素
const newElement1 = document.createElement('div');
newElement1.textContent = 'This is a div';
const newElement2 = document.createElement('p');
newElement2.textContent = 'This is a paragraph';
// 将新元素添加到 DocumentFragment 中
fragment.appendChild(newElement1);
fragment.appendChild(newElement2);
// 将 DocumentFragment 一次性附加到文档中
document.body.appendChild(fragment);
在这个例子中,newElement1
和 newElement2
被添加到 fragment
中,然后 fragment
被一次性添加到文档的 body
中。这种方法减少了直接操作 DOM 的次数,提高了性能。
示例2:与 Range 一起使用
// 假设你有以下 HTML 结构
// <div id="content">This is <b>some</b> content</div>
// 创建一个 Range
const range = document.createRange();
const contentDiv = document.getElementById('content');
// 设置 Range 覆盖范围
range.selectNodeContents(contentDiv);
// 提取范围内的内容到 DocumentFragment 中
const fragment = range.extractContents();
// 查看提取的内容
console.log(fragment);
// 可以将提取的内容重新附加到另一个节点中
document.body.appendChild(fragment);
在这个例子中,我们使用 Range
对象来选择 contentDiv
的内容,然后提取这些内容到一个 DocumentFragment
中。这个 DocumentFragment
包含了 contentDiv
的所有子节点,原始 contentDiv
的内容被清空。
Selection 中的 Range
在 JavaScript
中,Range
和 Selection
是两个密切相关的概念,用于处理和操作用户在页面上选择的文本或元素。Range
表示文档中的连续区域,而 Selection
代表用户的当前选择,它可以包含一个或多个 Range
。
document.addEventListener('mouseup', () => {
const selection = window.getSelection();
if (selection.rangeCount > 0) {
const range = selection.getRangeAt(0); // 获取第一个 Range
console.log('Selected text:', range.toString());
// 示例操作:在选区开始处插入文本
const newNode = document.createTextNode('【插入文本】');
range.insertNode(newNode);
}
});
手动划选时通常都只有一个 Range
,都是连续的选区。多选区一般是自行创建的:
document.getElementById('createRanges').addEventListener('click', () => {
const selection = window.getSelection();
selection.removeAllRanges(); // 清除任何现有的 Range
const range1 = document.createRange();
const range2 = document.createRange();
const para1 = document.getElementById('para1').firstChild;
const para2 = document.getElementById('para2').firstChild;
const para3 = document.getElementById('para3').firstChild;
range1.setStart(para1, 0);
range1.setEnd(para1, para1.length);
range2.setStart(para3, 0);
range2.setEnd(para3, para3.length);
selection.addRange(range1);
selection.addRange(range2);
console.log('Selection contains', selection.rangeCount, 'ranges.');
});