javascript基础学习系列二百五十九:简单选择和复杂选择
通过范围选择文档中某个部分最简单的方式,就是使用 selectNode()或 selectNodeContents() 方法。这两个方法都接收一个节点作为参数,并将该节点的信息添加到调用它的范围。selectNode()方 法选择整个节点,包括其后代节点,而 selectNodeContents()只选择节点的后代。假设有如下 HTML:
<!DOCTYPE html>
<html>
<body>
<p id="p1"><b>Hello</b> world!</p>
</body>
</html>
以下 JavaScript 代码可以访问并创建相应的范围:
let range1 = document.createRange(),
range2 = document.createRange(),
p1 = document.getElementById("p1");
range1.selectNode(p1);
range2.selectNodeContents(p1);
例子中的这两个范围包含文档的不同部分。range1 包含
元素及其所有后代,而 range2 包含 元素、文本节点"Hello"和文本节点" world!"
调用 selectNode()时,startContainer、endContainer 和 commonAncestorContainer 都 等于传入节点的父节点。在这个例子中,这几个属性都等于 document.body。startOffset 属性等于 传入节点在其父节点 childNodes 集合中的索引(在这个例子中,startOffset 等于 1,因为 DOM 的合规实现把空格当成文本节点),而 endOffset 等于 startOffset 加 1(因为只选择了一个节点)。
在调用 selectNodeContents()时,startContainer、endContainer 和 commonAncestor Container 属性就是传入的节点,在这个例子中是
元素。startOffset 属性始终为 0,因为范围 从传入节点的第一个子节点开始,而 endOffset 等于传入节点的子节点数量(node.child Nodes.length),在这个例子中等于 2。
在像上面这样选定节点或节点后代之后,还可以在范围上调用相应的方法,实现对范围中选区的更 精细控制。
setStartBefore(refNode),把范围的起点设置到 refNode 之前,从而让 refNode 成为选 区的第一个子节点。startContainer 属性被设置为 refNode.parentNode,而 startOffset 属性被设置为 refNode 在其父节点 childNodes 集合中的索引。
setStartAfter(refNode),把范围的起点设置到 refNode 之后,从而将 refNode 排除在选 区之外,让其下一个同胞节点成为选区的第一个子节点。startContainer 属性被设置为 refNode.parentNode,startOffset 属性被设置为 refNode 在其父节点 childNodes 集合 中的索引加 1。
setEndBefore(refNode),把范围的终点设置到 refNode 之前,从而将 refNode 排除在选区 21 之外、让其上一个同胞节点成为选区的最后一个子节点。endContainer 属性被设置为 refNode. parentNode,endOffset 属性被设置为 refNode 在其父节点 childNodes 集合中的索引。
setEndAfter(refNode),把范围的终点设置到 refNode 之后,从而让 refNode 成为选区的 最后一个子节点。endContainer 属性被设置为 refNode.parentNode,endOffset 属性被 设置为 refNode 在其父节点 childNodes 集合中的索引加 1。
调用这些方法时,所有属性都会自动重新赋值。不过,为了实现复杂的选区,也可以直接修改这些 属性的值。
复杂选择
要创建复杂的范围,需要使用 setStart()和 setEnd()方法。这两个方法都接收两个参数:参照节点 和偏移量。对 setStart()来说,参照节点会成为 startContainer,而偏移量会赋值给 startOffset。
对 setEnd()而言,参照节点会成为 endContainer,而偏移量会赋值给 endOffset。 25
使用这两个方法,可以模拟 selectNode()和 selectNodeContents()的行为。比如:
let 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);
注意,要选择节点(使用 range1),必须先确定给定节点(p1)在其父节点 childNodes 集合中 的索引。而要选择节点的内容(使用 range2),则不需要这样计算,因为可以直接给 setStart()和 setEnd()传默认值。虽然可以模拟 selectNode()和 selectNodeContents(),但 setStart()和 setEnd()真正的威力还是选择节点中的某个部分。
假设我们想通过范围从前面示例中选择从"Hello"中的"llo"到" world!"中的"o"的部分。很简 单,第一步是取得所有相关节点的引用,如下面的代码所示:
let p1 = document.getElementById("p1"),
helloNode = p1.firstChild.firstChild,
worldNode = p1.lastChild
文本"Hello"其实是
的孙子节点,因为它是的子节点。为此可以使用 p1.firstChild 取 得,而使用 p1.firstChild.firstChild 取得"Hello"这个文本节点。文本节点" world!"是
的第二个(也是最后一个)子节点,因此可以使用 p1.lastChild 来取得它。然后,再创建范围,指 定其边界,如下所示:
let range = document.createRange();
range.setStart(helloNode, 2);
range.setEnd(worldNode, 3);
因为选区起点在"Hello"中的字母"e"之后,所以要给 setStart()传入 helloNode 和偏移量 2 ("e"后面的位置,"H"的位置是 0)。要设置选区终点,则要给 setEnd()传入 worldNode 和偏移量 3,
即不属于选区的第一个字符的位置,也就是"r"的位置 3(位置 0 是一个空格)。展示了范围对 应的选区。
因为 helloNode 和 worldNode 是文本节点,所以它们会成为范围的 startContainer 和 endContainer,这样 startOffset 和 endOffset 实际上表示每个节点中文本字符的位置,而不是子 节点的位置(传入元素节点时的情形)。而 commonAncestorContainer 是
元素,即包含这两个节 点的第一个祖先节点。
当然,只选择文档中的某个部分并不是特别有用,除非可以对选中部分执行操作。
更多推荐

所有评论(0)