DOM2中的Range

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() 方法。 这个方法接受一个参数, 即环绕范围内容的节点。 在环绕范围插入内容时, 后台会执行下列步骤:

  1. 提取出范围中的内容(类似执行 extractContent());
  2. 将给定节点插入到文档中原来范围所在的位置上;
  3. 将文档片段的内容添加到给定节点中。

折叠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。

猜你喜欢

转载自blog.csdn.net/u013137242/article/details/81049292