DOM2 级在 Document 类型中定义了 createRange()方法。在兼容 DOM 的浏览器中,这个方法属于 document 对象。使用 hasFeature()或者直接检测该方法,都可以确定浏览器是否支持范围。
var supportsRange = document.implementation.hasFeature("Range", "2.0");
var alsoSupportsRange = (typeof document.createRange == "function");
如果浏览器支持范围,那么就可以使用 createRange()来创建 DOM 范围,如下所示:
var range = document.createRange();
与节点类似,新创建的范围也直接与创建它的文档关联在一起,不能用于其他文档。创建了范围之后,接下来就可以使用它在后台选择文档中的特定部分。而创建范围并设置了其位置之后,还可以针对范围的内容执行很多种操作,从而实现对底层 DOM 树的更精细的控制。
每个范围由一个 Range 类型的实例表示,这个实例拥有很多属性和方法。下列属性提供了当前范围在文档中的位置信息。
- startContainer:包含范围起点的节点(即选区中第一个节点的父节点)。
- startOffset:范围在 startContainer 中起点的偏移量。如果 startContainer 是文本节点、注释节点或 CDATA 节点,那么 startOffset 就是范围起点之前跳过的字符数量。否则,startOffset 就是范围中第一个子节点的索引。
- endContainer:包含范围终点的节点(即选区中最后一个节点的父节点)。
- endOffset:范围在 endContainer 中终点的偏移量(与 startOffset 遵循相同的取值规则)。
- commonAncestorContainer:startContainer 和 endContainer 共同的祖先节点在文档树中位置最深的那个。
在把范围放到文档中特定的位置时,这些属性都会被赋值。
简单的DOM范围选择
最简的方式就是使用 selectNode()
或selectNodeContents()
。
两个方法都接受DOM节点作为参数,但是区别在于:selectNode()
方法选择整个节点,包括其子节点;而 selectNodeContents()
方法则只选择节点的
子节点。
复杂的DOM范围选择
复杂的范围选择,可能跨DOM节点,而且范围的开始位置也许只是在某个节点的中间位置。这时候需要用到的方法就是setStart()
和 setEnd()
。
操作 DOM 范围中的内容
deleteContents()
: 删除选区内容;extractContents()
: 与 deleteContents()方法相似,extractContents()也会从文档中移除范围选区。但这两个方 法的区别在于,extractContents()会返回范围的文档片段。利用这个返回的值,可以将范围的内容 插入到文档中的其他地方。cloneContents()
: 克隆选区,并不会删除原有选区;
插入DOM范围中的内容
利用范围,可以删除或复制内容,还可以像前面介绍的那样操作范围中的内容。使用 insertNode() 方法可以向范围选区的开始处插入一个节点。
除了向范围内部插入内容之外,还可以环绕范围插入内容,此时就要使用 surroundContents() 方法。 这个方法接受一个参数, 即环绕范围内容的节点。 在环绕范围插入内容时, 后台会执行下列步骤:
- 提取出范围中的内容(类似执行 extractContent());
- 将给定节点插入到文档中原来范围所在的位置上;
- 将文档片段的内容添加到给定节点中。
折叠DOM范围
所谓 折叠范围,就是指范围中未选择文档的任何部分(范围开始位置和结束位置重合)。可以用文本框来描述折叠范围的过程。假设 文本框中有一行文本,你用鼠标选择了其中一个完整的单词。然后,你单击鼠标左键,选区消失,而光 标则落在了其中两个字母之间。
使用 collapse()方法来折叠范围,这个方法接受一个参数,一个布尔值,表示要折叠到范围的哪 一端。参数 true 表示折叠到范围的起点,参数 false 表示折叠到范围的终点。要确定范围已经折叠完 毕,可以检查 collapsed 属性。
比较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。
复制 DOM 范围
可以使用 cloneRange()
方法复制范围。这个方法会创建调用它的范围的一个副本。
var newRange = range.cloneRange();
新创建的范围与原来的范围包含相同的属性,而修改它的端点不会影响原来的范围。
清理 DOM 范围
在使用完范围之后,最好是调用 detach()
方法,以便从创建范围的文档中分离出该范围。调用 detach()
之后, 就可以放心地解除对范围的引用,从而让垃圾回收机制回收其内存了。来看下面的 例子。
range.detach();//从文档中分离
range = null; //解除引用
在使用范围的最后再执行这两个步骤是我们推荐的方式。一旦分离范围,就不能再恢复使用了。
上述几个点的demo code
<!DOCTYPE html>
<html>
<body>
<p id="p1">
<strong>This is a line bolded text.</strong>
<br />And this is a plain text.
<br>
<span>Hello world. bu hahaha~~~</span>
</p>
<script>
const p1 = document.getElementById('p1'),
strong = document.getElementsByTagName('strong')[0],
span = document.getElementsByTagName('span')[0];
const range1 = document.createRange(),
range2 = document.createRange();
// 简单范围选择: 选择一个节点
range1.selectNode(strong);
console.log(`startContainer : ${range1.startContainer.nodeName}, startOffset: ${range1.startOffset}
\n endContainer : ${range1.endContainer.nodeName}, endOffset: ${range1.endOffset}`);
// 复杂范围选择: setStart() 和 setEnd() 方法
range2.setStart(p1, 1);
range2.setEnd(span.childNodes[0], 4);
console.log(`\nstartContainer : ${range2.startContainer.nodeName}, startOffset: ${range2.startOffset}
\n endContainer : ${range2.endContainer.nodeName}, endOffset: ${range2.endOffset}`);
// 操作 范围 中的 DOM
let newp = document.createElement('p');
newp.innerHTML = 'This line will be removed in <strong>3</strong> seconds.';
document.body.appendChild(newp);
let newpcopy = document.createElement('p');
newpcopy.innerHTML = ('extractContents will be remove in <strong>3</strong> seconds.')
document.body.appendChild(newpcopy);
range1.selectNode(newp); range2.selectNode(newpcopy);
let clonenewp = range1.cloneContents();
console.log(clonenewp);
setTimeout(() => {
range1.deleteContents();
newpcopy = range2.extractContents();
console.log(newpcopy);
}, 3000);
// 插入DOM范围中的内容
const range3 = document.createRange();
range3.selectNode(p1);
const txt = document.createTextNode('I am inserted by insertNode()');
range3.insertNode(txt);
// 环绕范围插入内容:surroundContents
range3.selectNode(txt); // 选择刚才创建的文本节点
const surSpan = document.createElement('span');
surSpan.style.color = 'red';
range3.surroundContents(surSpan);
// 折叠DOM范围
console.log(range3.collapsed); // false
range3.collapse();
console.log(range3.collapsed); // true;
// DOM 范围开始位置和结束位置重合
console.log(range3.startContainer === range3.endContainer, range3.startOffset === range3.endOffset);
// 比较DOM 范围
const range4 = document.createRange();
range4.selectNode(p1.firstChild);
range3.setStart(p1.firstChild, 4); range3.setEnd(p1.childNodes[1].firstChild, 0);
range2.setStart(p1, 0); range2.setEnd(p1.firstChild, 0);
range1.setStart(p1.firstChild, 0); range1.setEnd(p1.childNodes[3], 0);
// Range.START_TO_START(0):比较第一个范围和第二个范围的起点;
// Range.START_TO_END(1):比较第一个范围的起点和第二个范围的终点;
// Range.END_TO_END(2):比较第一个范围和第二个范围的终点;
// Range.END_TO_START(3):比较第一个范围的终点和第一个范围的起点。
console.log(range4.compareBoundaryPoints(Range.START_TO_START, range1)); // - 1
console.log(range4.compareBoundaryPoints(Range.START_TO_END, range2)); // 1
console.log(range4.compareBoundaryPoints(Range.END_TO_START, range3)); // -1
// 复制 DOM 范围
const range5 = range4.cloneRange();
console.log(range4.compareBoundaryPoints(Range.START_TO_START, range5)); // 0
console.log(range4.compareBoundaryPoints(Range.END_TO_END, range5)); // 0
// 清理DOM范围
range5.detach();
range4.detach();
range3.detach();
console.log(range5.startContainer); // 觉着应该是null, 但是实际并没有释放
range3 = null; range4 = null; range5 = null; // 释放对象引用
</script>
</body>
</html>
IE8 中到范围
虽然 IE9 支持 DOM 范围,但 IE8 及之前版本不支持 DOM 范围。不过,IE8 及早期版本支持一种类 似的概念,即文本范围(text range)。文本范围是 IE 专有的特性,其他浏览器都不支持。顾名思义,文 本范围处理的主要是文本(不一定是 DOM 节点)。通过<body>
、<button>
、<input>
和<textarea>
等这几个元素,可以调用 createTextRange()
方法来创建文本范围。以下是一个例子:
var range = document.body.createTextRange();
像这样通过 document 创建的范围可以在页面中的任何地方使用(通过其他元素创建的范围则只能 在相应的元素中使用)。与 DOM 范围类似,使用 IE 文本范围的方式也有很多种。
用 IE 范围实现简单的选择
选择页面中某一区域的最简单方式,就是使用范围的 findText()方法。这个方法会找到第一次出 现的给定文本,并将范围移过来以环绕该文本。如果没有找到文本,这个方法返回 false;否则返回 true。