JS 12.4 范围

12.4 范围

通过范围可以选择文档中的一个区域,而不必考虑节点的界限(选择在后台完成,对用户是不可见的);

在常规的DOM操作不能更有效地修改文档时,使用范围往往可以达到目的;

DOM中的范围

createRange();

var supportsRange = document.implementation.hasFeature("Range", "2.0");
var alsoSupportsRange = (typeof document.createRange == "function");

每个范围由一个Range类型的实例表示;

1.startContainer:包含范围起点的节点(即选区中第一个节点的父节点);

2.startOffset:

范围在startContainer中起点的偏移量;

如果startContainer是文本节点,注释节点或CDATA节点,

那么startOffset就是范围起点之前跳过的字符数量;

否则,startOffset就是范围中第一个子节点的索引;

3.endContainer:包含范围终点的节点(即选区中最后一个节点的父节点);

4.endOffset:范围在endContainer 中终点的偏移量(与startOffset遵循相同的取值规则);

5.commonAncestorContainer:startContainer和endContainer共同的祖先节点在文档树中位置最深的那个;

在把范围放到文档中特定的位置时,这些属性都会被赋值;

用DOM范围实现简单选择

selectNode(),selectNodeContents();

接受一个参数,即一个DOM 节点,然后使用该节点中的信息来填充范围;

selectNode()方法选择整个节点,包括其子节点;

selectNodeContents()方法则只选择节点的子节点;

此外还有setStartBefore(refNode),setStartAfter(refNode),setEndBefore(refNode),setEndAfter(refNode);

在调用这些方法时,所有属性都会自动为你设置好;

不过,要想创建复杂的范围选区,也可以直接指定这些属性的值;

用DOM范围实现复杂选择

setStart(),setEnd();

接受两个参数:一个参照节点和一个偏移量值;

var range1 = document.createRange();
range2 = document.createRange();
p1 = document.getElementById("p1");
p1Index = -1;
i, len;
for (i = 0, len = p1.parentNode.childNodes.length; i < len; i++) {
    if (p1.parentNode.childNodes[i] == p1) {
        p1Index = i;
        break;
    }
}
range1.setStart(p1.parentNode, p1Index);
range1.setEnd(p1.parentNode, p1Index + 1);
range2.setStart(p1, 0);
range2.setEnd(p1, p1.childNodes.length);

模仿selectNode()和selectNodeContents()并不是setStart()和setEnd()的主要用途,

它们更胜一筹的地方在于能够选择节点的一部分;

	<!DOCTYPE html>
	<html>
	<body>
	<p id="p1"><b>Hello</b> world!</p>
	//            01234    0123456
	</body>
	</html>

选择节点

var p1 = document.getElementById("p1");
helloNode = p1.firstChild.firstChild;
worldNode = p1.lastChild;

var range = document.createRange();
range.setStart(helloNode, 2);
rang e.setEnd(worldNode, 3);

从”Hello”的”llo”到”world!”的”o”;

操作DOM范围中的内容

deleteContents(),删除范围包含的内容;

与deleteContents()方法相似,extractContents()也会从文档中移除范围选区;

但这两个方法的区别在于,extractContents()会返回范围的文档片段;

利用这个返回的值,可以将范围的内容插入到文档中的其他地方;

var p1 = document.getElementById("p1");
helloNode = p1.firstChild.firstChild;
worldNode = p1.lastChild;
range = document.createRange();
range.setStart(helloNode, 2);
range.setEnd(worldNode, 3);
var fragment = range.extractContents();
p1.parentNode.appendChild(fragment);

// 结果
<p><b>He</b>rld!</p>
<b>llo</b> wo

cloneContents()创建范围对象的一个副本,然后在文档的其他地方插入该副本;

var p1 = document.getElementById("p1"),
    helloNode = p1.firstChild.firstChild,
    worldNode = p1.lastChild,
    range = document.createRange();
range.setStart(helloNode, 2);
range.setEnd(worldNode, 3);
var fragment = range.cloneContents();
p1.parentNode.appendChild(fragment);

// 结果
<p><b>Hello</b> world!</p>
<b>llo</b> wo

在调用上面介绍的方法之前,拆分的节点并不会产生格式良好的文档片段;

换句话说,原始的HTML在DOM被修改之前会始终保持不变;

关于格式良好的文档片段,参见page353第三节;

插入DOM范围中的内容

insertNode()向范围选区的开始处插入一个节点;

向86行的html插入;

<span style="color: red">Inserted text</span>

// insert
var p1 = document.getElementById("p1");
helloNode = p1.firstChild.firstChild;
worldNode = p1.lastChild;
range = document.createRange();
range.setStart(helloNode, 2);
range.setEnd(worldNode, 3);
var span = document.createElement("span");
span.style.color = "red";
span.appendChild(document.createTextNode("Inserted text"));
range.insertNode(span);

// 结果
<p id="p1"><b>He<span style="color: red">Inserted text</span>llo</b> world</p>

由于这里没有使用上一节介绍的方法,结果原始的HTML并没有添加或删除<b>元素;

surroundContents()环绕插入;

接受一个参数,即环绕范围内容的节点;

执行步骤:

1.提取出范围中的内容(类似执行extractContent());

2.将给定节点插入到文档中原来范围所在的位置上;

3.将文档片段的内容添加到给定节点中;

var p1 = document.getElementById("p1");
helloNode = p1.firstChild.firstChild;
worldNode = p1.lastChild;
range = document.createRange();
range.selectNode(helloNode);
var span = document.createElement("span");
span.style.backgroundColor = "yellow";
range.surroundContents(span);

// 结果
<p><b><span style="background-color:yellow">Hello</span></b> world!</p>

折叠DOM范围

collapse()折叠范围;

接受一个参数,一个布尔值,表示要折叠到范围的哪一端;

参数true表示折叠到范围的起点,参数false表示折叠到范围的终点;

检测是否折叠;

<p id="p1">Paragraph 1</p><p id="p2">Paragraph 2</p>

//检测
var p1 = document.getElementById("p1"),
p2 = document.getElementById("p2"),
range = document.createRange();
range.setStartAfter(p1);
range.setStartBefore(p2);
alert(range.collapsed); //输出true

在这个例子中,新创建的范围是折叠的,因为p1的后面和p2的前面什么也没有;

比较DOM范围

compareBoundaryPoints()确定多个范围是否有公共的边界;

接受两个参数:表示比较方式的常量值和要比较的范围;

常量值:

1.Range.START_TO_START(0):比较第一个范围和第二个范围的起点;

2.Range.START_TO_END(1):比较第一个范围的起点和第二个范围的终点;

3.Range.END_TO_END(2):比较第一个范围和第二个范围的终点;

4.Range.END_TO_START(3):比较第一个范围的终点和第二个范围的起点;

第四条pdf上的不对,对比MDN已经修正;

compares the end boundary-point of sourceRange to the start boundary-point of Range;

可能的返回值如下:

如果第一个范围中的点位于第二个范围中的点之前,返回-1;

如果两个点相等,返回0;

如果第一个范围中的点位于第二个范围中的点之后,返回1;

	       ---range1----------
<p id="p1"><b>Hello</b> world!</p>
           ---range2---

var range1 = document.createRange();
var range2 = document.createRange();
var p1 = document.getElementById("p1");
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

复制DOM范围

cloneRange()复制范围;

var newRange = range.cloneRange();

新创建的范围与原来的范围包含相同的属性,而修改它的端点不会影响原来的范围;

清理DOM范围

detach()分离出范围;

在使用完范围之后,最好是调用detach()方法,以便从创建范围的文档中分离出该范围;

调用detach()之后,就可以放心地解除对范围的引用,从而让垃圾回收机制回收其内存了;

range.detach(); //从文档中分离
range = null; //解除引用

一旦分离,不能再恢复使用;

IE8及更早版本中的范围

IE8及之前版本不支持DOM范围;

不过,IE8及早期版本支持一种类似的概念,即文本范围(text range);

文本范围是IE专有的特性,其他浏览器都不支持;

createTextRange();

用IE范围实现简单的选择

findText()这个方法会找到第一次出现的给定文本,并将范围移过来以环绕该文本;

如果没有找到文本,这个方法返回false;否则返回true;

<p id="p1"><b>Hello</b> world!</p>

var range = document.body.createTextRange();
var found = range.findText("Hello");

alert(found); //true
alert(range.text); //"Hello"

还可以接受方向参数,负值表示应该从当前位置向后搜索,而正值表示应该从当前位置向前搜索;

moveToElementText()接受一个DOM元素,并选择该元素的所有文本,包括HTML标签;

var range = document.body.createTextRange();
var p1 = document.getElementById("p1");
range.moveToElementText(p1);

alert(range.htmlText);

IE的范围没有任何属性可以随着范围选区的变化而动态更新;

parentElement()获取文本选区的父节点;与commonAncestorContainer类似,line36;

var ancestor = range.parentElement();

用IE范围实现复杂的选择

在IE中创建复杂范围的方法,就是以特定的增量向四周移动范围;

move(),moveStart(),moveEnd(),expand();

都接受两个参数:移动单位和移动单位的数量;

移动单位是下列一种字符串值;

1.”character”:逐个字符地移动;

2.”word”:逐个单词(一系列非空格字符)地移动;

3.”sentence”:逐个句子(一系列以句号,问号或叹号结尾的字符)地移动;

4.”textedit”:移动到当前范围选区的开始或结束位置;

range.moveStart("word", 2); //起点移动2个单词
range.moveEnd("character", 1); //终点移动1个字符
range.move("character", 5); //移动5 个字符

expand()方法的作用是将任何部分选择的文本全部选中;

例如,当前选择的是一个单词中间的两个字符,调用expand(“word”)可以将整个单词都包含在范围之内;

move()方法则首先会折叠当前范围(让起点和终点相等),然后再将范围移动指定的单位数量;

调用move()之后,范围的起点和终点相同,

因此必须再使用moveStart()或moveEnd()创建新的选区;

操作IE范围中的内容

text属性,pasteHTML();

var range = document.body.createTextRange();
range.findText("Hello");
range.text = "Howdy";

<p id="p1"><b>Howdy</b> world!</p>

var range = document.body.createTextRange();
range.findText("Hello");
range.pasteHTML("<em>Howdy</em>");

<p id="p1"><b><em>Howdy</em></b> world!</p>

在范围中包含HTML代码时,不应该使用pasteHTML(),

因为这样很容易导致不可预料的结果,很可能是格式不正确的HTML;

折叠IE范围

collapse(),传入true把范围折叠到起点,传入false把范围折叠到终点;

没有对应的collapsed属性让我们知道范围是否已经折叠完毕;

为此,必须使用boundingWidth属性,该属性返回范围的宽度(以像素为单位);

如果boundingWidth属性等于0,就说明范围已经折叠了;

var isCollapsed = (range.boundingWidth == 0);

比较IE范围

compareEndPoints()接受两个参数:比较的类型和要比较的范围;

比较类型取值:”StartToStart”,”StartToEnd”,”EndToEnd”和”EndToStart”;

这几种比较类型与比较DOM范围时使用的几个值是相同的;

返回值规则相同;

var range1 = document.body.createTextRange();
var range2 = document.body.createTextRange();
range1.findText("Hello world!");
range2.findText("Hello");
alert(range1.compareEndPoints("StartToStart", range2)); //0
alert(range1.compareEndPoints("EndToEnd", range2)); //1

isEqual()确定两个范围是否相等;

inRange()确定是否包含;

var range1 = document.body.createTextRange();
var range2 = document.body.createTextRange();
range1.findText("Hello World");
range2.findText("Hello");
alert("range1.isEqual(range2): " + range1.isEqual(range2)); //false
alert("range1.inRange(range2):" + range1.inRange(range2)); //true

复制IE范围

duplicate()复制文本范围;

var newRange = range.duplicate();

新创建的范围会带有与原范围完全相同的属性;