DOM2级遍历和范围模块定义了范围(range),通过范围可以选择文档中的一个区域,而不必考虑节点的界限。在常规的DOM操作不能有效地修改文档时,使用范围往往可以达到目的。
<!DOCTYPE html>
<html>
<body>
<p id="p1"><b>Hello</b> world!</p>
</body>
</html>
1.创建范围
const range = document.createRange();
每一个范围由一个Range类型的实例表示,Range拥有以下属性:
startContainer: 包含范围起点的节点(选中区第一个节点的父节点)
startOffset: 范围在startContainer中的偏移量
endContainer: 包含范围终点的节点(选中区最后一个节点的父节点)
endOffset: 范围在endContainer中的偏移量
commonAncestorContainer: startContainer和endContainer共同的祖先节点在文档树种位置最深的那个
2.用DOM范围实现简单选择
使用范围选择文档中的一部分,最简单的方式就是使用selectNode或selectNodeContents,这两个方法接受一个DOM节点作为参数。selectNode方法会选择整个节点(包括子节点),selectNodeContents方法只选择节点的子节点。
const range1 = document.createRange();
const range2 = document.createRange();
const p1 = document.querySelector("#p1");
range1.selectNode(p1); // 选中<p id="p1"><b>Hello</b> world!</p>
range2.selectNodeContents(p1); // 选中<b>Hello</b> world!
3.用DOM范围实现复杂选择
要创建复杂选择就需要用setStart和setEnd方法。这两个方法都接受两个参数:一个参照节点和一个偏移量。对于setStart来说,参照节点会变成startContainer,偏移量会变成startOffset。对于setEnd来说,参照节点会变成endCOntainer,偏移量会变成endOffset。
const p1 = document.querySelector("#p1");
const range = document.createRange();
const helloNode = p1.firstChild.firstChild;
const worldNode = p1.lastChild;
// 选区从"Hello"的"e"后面开始,到" world!"的"r"之前结束
range.setStart(helloNode, 2);
range.setEnd(worldNode, 3);
4.删除DOM范围中的内容
想要从文档中删除范围所包含的内容就需要用到deleteContents方法。
const p1 = document.querySelector("#p1");
const range = document.createRange();
const helloNode = p1.firstChild.firstChild;
const worldNode = p1.lastChild;
// 选区从"Hello"的"e"后面开始,到" world!"的"r"之前结束
range.setStart(helloNode, 2);
range.setEnd(worldNode, 3);
// 页面html会变成"<p id="p1"><b>He</b>rld!</p>"
range.deleteContents();
使用extractContents方法可以达到同样的效果,但区别在于它会返回删除的文档范围片段。利用这个返回值,可以将范围的内容插入到文档的其他地方。
const p1 = document.querySelector("#p1");
const range = document.createRange();
const helloNode = p1.firstChild.firstChild;
const worldNode = p1.lastChild;
// 选区从"Hello"的"e"后面开始,到" world!"的"r"之前结束
range.setStart(helloNode, 2);
range.setEnd(worldNode, 3);
// 页面html会变成"<p id="p1"><b>He</b>rld!</p>"
const fragment = range.extractContents();
p1.parentNode.appendChild(fragment);
另外一种做法,使用cloneContents方法创建范围的一个副本,然后再文档的其他地方插入该副本。
const p1 = document.querySelector("#p1");
const range = document.createRange();
const helloNode = p1.firstChild.firstChild;
const worldNode = p1.lastChild;
// 选区从"Hello"的"e"后面开始,到" world!"的"r"之前结束
range.setStart(helloNode, 2);
range.setEnd(worldNode, 3);
// 页面html会变成"<p id="p1"><b>He</b>rld!</p>"
const fragment = range.cloneContents();
p1.parentNode.appendChild(fragment);
5.插入DOM范围中的内容
使用insertNode方法可以向范围选区的开始处插入一个节点。
const p1 = document.querySelector("#p1");
const range = document.createRange();
const helloNode = p1.firstChild.firstChild;
const worldNode = p1.lastChild;
// 选区从"Hello"的"e"后面开始,到" world!"的"r"之前结束
range.setStart(helloNode, 2);
range.setEnd(worldNode, 3);
// 页面html会变成
// <p id="p1"><b>He<span style="color: red">Inserted text</span>llo</b> world!</p>"
const span = document.createElement("span");
span.style.color = "red";
span.appendChild(document.createTextNode("Inserted text"));
range.insertNode(span);
6. 折叠DOM范围
所谓折叠范围,就是指范围中未选择文档的任何部分。在折叠范围时,光标的位置会落在文档中的两个部分之间,可能是范围选区的开始位置,也可能是结束位置。
使用collapse方法来折叠范围,这个方法接受一个布尔值作为参数,表示折叠到范围的那一端,true表示折叠到范围的起点,false表示折叠到范围的终点。要确定范围是否已经折叠完毕,可以检查collapsed属性。
range.collapse(true); // 折叠到起点
alert(range.collapsed); // 输出true
检测某个范围是否处于折叠状态,可以帮助我们确定范围中的两个节点是否相邻。
<p id="p1">Paragraph 1</p><p id="p2">Paragraph 2</p>
const p1 = document.querySelector("#p1");
const p2 = document.querySelector("#p2");
const range = document.createRange();
range.setStartAfter(p1);
range.setEndBefore(p2);
alert(range.collapsed); // 输出true
7.比较DOM范围
在有多个范围的情况下,可以使用compareBoundaryPoints方法来确定这些范围是否有公共的边界。这个方法接受两个参数:表示比较方式的常量值和要比较的范围。表示比较方式的常量值如下:
Range.START_TO_START(0):比较第一个范围和第二个范围的起点
Range.START_TO_END(1):比较第一个范围的起点和第二个范围的终点
Range.END_TO_END(2):比较第一个范围和第二个范围的终点
Range.END_TO_START(3):比较第一个范围的终点和第二个范围的起点
compareBoundaryPoints方法可能的返回值如下:
如果第一个范围中的点位于第二个范围中的点之前,则返回-1
如果第一个范围中的点第二个范围中的点相等,则返回0
如果第一个范围中的点位于第二个范围中的点之后,则返回1
const p1 = document.querySelector("#p1");
const range1 = document.createRange();
cosnt range2 = document.createRange();
range1.selectNodeContents(p1);
range2.selectNodeContents(p1);
range2.setEndBefore(p1.lastChild);
alert(range1.compareBoundaryPoints(Range.START_TO_START, range2); // 0
alert(range1.compareBoundaryPoints(Range.END_TO_END, range2); // 1
8.清理DOM范围
在使用玩范围之后,最好调用detach方法,以便从创建范围的文档中分离该范围。调用detach方法后,就可以放心的解除对范围的引用,从而让垃圾回收机制回收其内存。
range.detach(); // 从文档中分离
range = null; // 解除引用