Javascript 标准 DOM Range 操作 (2)

复杂 DOM Range

建立复杂的 DOM range 需要使用 setStart() 和 setEnd() 两个方法。 这两个方法有两个参数:

  • 一个参数是一个节点 (node) 引用;
  • 另一个参数是偏移量 (offset) 。

这两个方法的功能:

  • setStart(node, offset) 节点的引用是 startContainer ,偏移则是 startOffset ;
  • setEnd(node, offset) 节点引用为 endContainer ,偏移就是 endOffset 。

使用这两个方法与 selectNode() 和 selectNodeContents() 方法相似。
比如下面 useRanges() 函数的前一个示例,可以使用 setStart() 和 setEnd():

function useRanges() {
	var oRange1 = document.createRange();
	var oRange2 = document.createRange();
	var oP1 = document.getElementById("p1");
	var iP1Index = -1;
	for (var i=0; i < oP1.parentNode.childNodes.length; i++) {
		if (oP1.parentNode.childNodes[i] == oP1) {
			iP1Index = i;
			break;
		}
	}
	
	oRange1.setStart(oP1.parentNode, iP1Index);
	oRange1.setEnd(oP1.parentNode, iP1Index + 1);
	oRange2.setStart(oP1, 0);
	oRange2.setEnd(oP1, oP1.childNodes.length);
	//textbox assignments here
}

注意,这个选择节点时的代码 (oRange1) ,你必须指定 oP1 父节点里所有 childNodes 集合里的一个索引。
而选择内容时的代码 (oRange2) ,则不需要额外考虑。

  • 从刚才的例子来从这段 HTML 里<p id="p1"><b>Hello</b> World</p>
  • 选择从 「 hello 」 中的 「 llo 」 开始到从 「 World 」 中的 「 Wo 」 开始的 Range ,我们用 setStart() 和 setEnd() ,很容易就做到了。

首先,我们必须用常规的 DOM 方法得到文本节点的引用还有就是容器 p1 的引用。

var oP1 = document.getElementById("p1");
var oHello = oP1.firstChild.firstChild;
var oWorld = oP1.lastChild;
  • 文本 Hello 实际上是容器 p1 的孙子节点,所以我们可以用 oP1.firstChild 得到 <b>
  • oP1.firstChild.firstChild 也就是 「 Hello 」 文本节点的引用了,而 「 World 」 则就是容器 p1 的最后一个节点了。

下一步,创建 Range 然后设置偏移 (offset) :

var oP1 = document.getElementById("p1");
var oHello = oP1.firstChild.firstChild;
var oWorld = oP1.lastChild;
var oRange = document.createRange();
oRange.setStart(oHello, 2);
oRange.setEnd(oWorld, 3);
  • 用 setStart() 偏移 (offset) 为 2 。
    因为字母 l 在该文本节点中 ( 即 Hello 中 ) 的位置是 2 , ( 位置是从 0 开始计算的 ) ,
  • 用 setEnd() 的偏移 (offset) 为 3 。
    原因同上,需要注意的是 World 前面有一个空格,空格也是占位置的。如图:

注意: (Mozilla DOM Range bug #135928) 在 Mozilla 低版本浏览器 执行此 Range 方法时,如果 setStart() 和 setEnd() 都指向同一个文本节点会出现异常。

用 DOM Range 操作

当创建一个 Range 对象时,在 Range 里的所有对象之上,已经创建了一个文档的 fragment 节点。
在这之前, Range 对象必须合格证你选择的这段 Range 是一个 well-formed (格式良好)。
比如以面这段 Range :

在这里 HTML 并不是一个 well-formed 。
上面也说过,当创建一个 Range 时,会自动产生一个 fragment 。
在这里 framgment 自动动态的添加一些元素,以保证 Range 的正确性:

<p><b>He</b><b>llo</b> World</p>

也就是自动加上了开始标签「<b>」,使得整个 Range 变为:

<b>llo</b> Wo ,

fragment 的示意图:

fragment 创建后,你就可以用 Range 的一些方法来操作它了。

最简单的一个操作就是:
deleteContents() 方法,这个方法将删除 Range 选中的部分,在上面的操作之后进行 deleteContents() ,那么余下的 HTML 就为:

<p><b>He</b>rld</p>

之所以加上闭合标签 </b>,上面也说了,也是 Range 为了确保它是 well-formed 。

extractContents() 方法类似于 deleteContents() ,但具体操作不同。
extractContents() 是将选中的 Range 从 DOM 树中移到一个 fragment 中,并返回此 fragment ,复制下面这些代码然后在 Mozilla Firefox 里运行,看看结果你就明白了。——删除的「<b>llo</b> Wo 」作为一个 fragment 被添加到 body 的末端。

<p id="p1"><b>Hello</b> World</p>
<script>
var oP1 = document.getElementById("p1");
var oHello = oP1.firstChild.firstChild;
var oWorld = oP1.lastChild;
var oRange = document.createRange();
oRange.setStart(oHello, 2);
oRange.setEnd(oWorld, 3);
var oFragment = oRange.extractContents();
document.body.appendChild(oFragment);
</script>

cloneContents() 方法可以克隆选中 Range 的 fragment ,比如:

<p id="p1"><b>Hello</b> World</p>
<script>
var oP1 = document.getElementById("p1");
var oHello = oP1.firstChild.firstChild;
var oWorld = oP1.lastChild;
var oRange = document.createRange();
oRange.setStart(oHello, 2);
oRange.setEnd(oWorld, 3);
var oFragment = oRange.cloneContents();
document.body.appendChild(oFragment);
</script>

这个方法类似于 extractContents() ,但是不是删除,而是克隆。

Comments