通过范围选择文档中某个部分最简单的方式,就是使用 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 是

元素,即包含这两个节 点的第一个祖先节点。
当然,只选择文档中的某个部分并不是特别有用,除非可以对选中部分执行操作。

更多推荐