JS高级程序设计——第12章 DOM2和DOM3 12.4 范围

一、DOM中的范围

为了让开发人员更方便地控制页面,“DOM2
级遍历和范围”模块定义了“范围”(range)接口。通过范围可以选择文档中的一个区域,而不必考虑节点的界限(选择在后台完成,对用户是不可见的)。
在常规的 DOM 操作不能更有效地修改文档时,使用范围往往可以达到目的。Firefox、Opera、Safari 和 Chrome 都支持 DOM 范围。IE 以专有方式实现了自己的范围特性。

1、 使用createRange()方法创建DOM范围
  1. DOM2 级在 Document 类型中定义了 createRange()方法。在兼容 DOM 的浏览器中,这个方法属于 document 对象。

  2. 使用 hasFeature()或者直接检测该方法,都可以确定浏览器是否支持范围
    在这里插入图片描述

  3. 如果浏览器支持范围,那么就可以使用 createRange()来创建 DOM 范围,如下所示:

    var range = document.createRange();
    
  • 与节点类似,新创建的范围也直接与创建它的文档关联在一起,不能用于其他文档。
  • 创建了范围之后,接下来就可以使用它在后台选择文档中的特定部分。而创建范围并设置了其位置之后,还可以针对范围的内容执行很多种操作,从而实现对底层 DOM 树的更精细的控制。
  1. 每个范围由一个 Range 类型的实例表示,这个实例拥有很多属性和方法。下列属性提供了当前范围在文档中的位置信息
    在这里插入图片描述
    endOffset:范围内最后一个子节点前面隔了几个节点加上1(范围内最后一个子节点本身),也等于startOffset的值加上范围子节点的数量
    在把范围放到文档中特定的位置时,这些属性都会被赋值
2、用 DOM 范围实现简单选择
  1. 使用 selectNode()selectNodeContents():要使用范围来选择文档中的一部分,最简的方式就是使用 selectNode()或 selectNodeContents()。
  • 这两个方法都接受一个参数,即一个 DOM 节点,然后使用该节点中的信息来填充范围。其中,selectNode()方法选择整个节点,包括其子节点;而selectNodeContents()方法则只选择节点的子节点。以下面的 HTML 代码为例。

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

    我们可以使用下列代码来创建范围

     var range1 = document.createRange();
    	range2 = document.createRange();
    	p1 = document.getElementById("p1"); 
    	range1.selectNode(p1);
    	range2.selectNodeContents(p1);
    

    这里创建的两个范围包含文档中不同的部分:rang1 包含<p/>元素及其所有子元素,而 rang2 包含<b/>元素、文本节点"Hello"和文本节点"world!"(如图 12-6 所示)。
    在这里插入图片描述

  • 在调用 selectNode()时,startContainer、endContainer 和 commonAncestorContainer 都等于传入节点的父节点,也就是这个例子中的 document.body

  • startOffset 属性等于给定节点在其父节点的 childNodes 集合中的索引【p在其父节点子节点集合,就是body的子节点结合中的索引】(在这个例子中是 1——因为兼容 DOM 的浏览器将空格算作一个文本节点)endOffset 等于 startOffset 加 1(因为只选择了一个节点)。

  • 在调用 selectNodeContents()时(只包括p中的子节点),startContainer、endContainer 和 commonAncestorContainer 等于传入的节点,即这个例子中的<p>元素。而 startOffset 属性始终等于 0(p标签和b标签之间没有空格),因为范围从给定节点的第一个子节点开始。最后,endOffset 等于子节点的数量(node.childNodes.length),在这个例子中是 2【startOffset-0 +node.childNodes.length-2 = 2】。

  1. 此外,为了更精细地控制将哪些节点包含在范围中,还可以使用下列方法。
    在这里插入图片描述
    在这里插入图片描述
    【refNode前有refNode的索引个节点,endOffset = 最后一个子节点的索引+1 = refNode的索引】
3、用 DOM 范围实现复杂选择
  1. 要创建复杂的范围就得使用 setStart()setEnd()方法。这两个方法都接受两个参数一个参照节点和一个偏移量值
  2. setStart()来说:参照节点会变成 startContainer,而偏移量值会变成 startOffset。
    对于 setEnd()来说:参照节点会变成 endContainer,而偏移量值会变成 endOffset。
  3. 可以使用这两个方法来模仿 selectNode()和 selectNodeContents()。来看下面的例子:
    在这里插入图片描述
  4. setStart() 和 setEnd()的主要用途在于能够选择节点的一部分
  • 假设你只想选择前面 HTML 示例代码中从"Hello"的"llo"到"world!“的"o”——很容易做到。 第一步是取得所有节点的引用,如下面的例子所示:
 var p1 = document.getElementById("p1");
     helloNode = p1.firstChild.firstChild;
     worldNode = p1.lastChild;
var 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 上还有一个空格)。如图 12-7 所示
    在这里插入图片描述
4、操作 DOM 范围中的内容
  1. 在创建范围时 ,内部会为这个范围创建一个文档片段范围所属的全部节点都被添加到了这个文档片段中
  • 为了创建这个文档片段,范围内容的格式必须正确有效。在前面的例子中,我们创建的选区分别开始和结束于两个文本节点的内部,因此不能算是格式良好的 DOM 结构,也就无法通过 DOM 来表 示。但是,范围知道自身缺少哪些开标签和闭标签,它能够重新构建有效的 DOM 结构以便我们对其进 行操作。
  • 对于前面的例子而言,范围经过计算知道选区中缺少一个开始的<b>标签,因此就会在后台动态加 入一个该标签,同时还会在前面加入一个表示结束的</b>标签以结束"He"。于是,修改后的 DOM 就变成了如下所示。
<p><b>He</b><b>llo</b> world!</p>
  • 另外,文本节点"world!"也被拆分为两个文本节点,一个包含"wo",另一个包含"rld!"。最终的 DOM 树如图 12-8 所示,右侧是表示范围的文档片段的内容。
    在这里插入图片描述
  1. 像这样创建了范围之后,就可以使用各种方法对范围的内容进行操作了(注意,表示范围的内部文档片段中的所有节点,都只是指向文档中相应节点的指针)
  • 第一个方法:也是最容易理解的方法,就是 deleteContents()。这个方法能够从文档中删除范围所包含的内容。例如:

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

    执行以上代码后,页面中会显示如下 HTML 代码:

    <p><b>He</b>rld!</p>
    
  • 与 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);

在这个例子中,我们将提取出来的文档片段添加到了文档<body>元素的末尾。(记住,在将文档片 段传入 appendChild()方法中时,添加到文档中的只是片段的子节点,而非片段本身。)结果得到如下 HTML 代码:

 <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);
    

    这个方法与 extractContents()非常类似,因为它们都返回文档片段。它们的主要区别在于, cloneContents()返回的文档片段包含的是范围中节点的副本,而不是实际的节点。执行上面的操作 后,页面中的 HTML 代码应该如下所示:

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

    有一点请读者注意,那就是在调用上面介绍的方法之前,拆分的节点并不会产生格式良好的文档片段。换句话说,原始的 HTML 在 DOM 被修改之前会始终保持不变。

    有疑问:与前面说的不符合呀?格式良好的文档片段不是在创建范围时就自动补全了吗?这里应该怎样理解?经测试在调用上述方法之前,原始的 HTML 确实未发生变化

5、插入DOM范围中的内容
  1. insertNode() 方法:可以向范围选区的开始处插入一个节点。假设我们想在前面例子中的 HTML 前面插入以下 HTML代码:
    在这里插入图片描述
    运行以上 JavaScript 代码,就会得到如下 HTML 代码:

    //注意:此时没有自动补齐标签
    <p id="p1"><b>He<span style="color: red">Inserted text</span>llo</b> world</p>
    

    注意,<span>正好被插入到了"Hello"中的"llo"前面,而该位置就是范围选区的开始位置。还要注意的是,由于这里没有使用上一节介绍的方法,结果原始的 HTML 并没有添加或删除<b>元素。使用 这种技术可以插入一些帮助提示信息,例如在打开新窗口的链接旁边插入一幅图像。

  2. surroundContents() 方法:除了向范围内部插入内容之外,还可以环绕范围插入内容,此时就要使用 surroundContents() 方法。这个方法接受一个参数,即环绕范围内容的节点。在环绕范围插入内容时,后台会执行下列步骤。
    (1) 提取出范围中的内容(类似执行 extractContent());
    (2) 将给定节点插入到文档中原来范围所在的位置上;
    (3) 将文档片段的内容(存储着范围所有节点)添加到给定节点中。

    可以使用这种技术来突出显示网页中的某些词句,例如下列代码:
    在这里插入图片描述
    给范围选区加上一个黄色的背景。得到的 HTML 代码如下所示:

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

为了插入<span>,范围必须包含整个 DOM 选区(不能仅仅包含选中的 DOM 节点)。【没懂这句话什么意思?】

6、折叠 DOM 范围
  1. 所谓折叠范围,就是指范围中未选择文档的任何部分
  2. 可以用文本框来描述折叠范围的过程。假设文本框中有一行文本,你用鼠标选择了其中一个完整的单词。然后,你单击鼠标左键,选区消失,而光标则落在了其中两个字母之间。
  • 同样,在折叠范围时,其位置会落在文档中的两个部分之间,可能是范围选区的开始位置,也可能是结束位置。图 12-9 展示了折叠范围时发生的情形。在这里插入图片描述

  • 使用 collapse()方法来折叠范围,这个方法接受一个参数,一个布尔值,表示要折叠到范围的哪一端。参数true表示折叠到范围的起点,参数false表示折叠到范围的终点。要确定范围已经折叠完毕,可以检查 collapsed 属性,如下所示:

    range.collapse(true); //折叠到起点 
    alert(range.collapsed); //输出 true
    
  1. 折叠范围的作用:检测某个范围是否处于折叠状态,可以帮我们确定范围中的两个节点是否紧密相邻。例如,对于下面的 HTML 代码:

    <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.setEndBefore(p2); 
    alert(range.collapsed); //输出 true
    

在这个例子中,新创建的范围是折叠的,因为 p1 的后面和 p2 的前面什么也没有。【没有理解:上面说可以确定范围中的两个节点是否紧密相邻,但p1和p2不在范围内啊】

7、比较DOM范围
  1. compareBoundaryPoints()方法:在有多个范围的情况下,可以使用compareBoundaryPoints()方法来确定这些范围是否有公共的边界(起点或终点)
  • 这个方法接受两个参数:表示比较方式的常量值和要比较的范围。表示比较方式的常量值如下所示。
    在这里插入图片描述
  • compareBoundaryPoints()方法可能的返回值如下:
    如果第一个范围中的点位于第二个范围中的点之前,返回-1;
    如果两个点相等,返回 0;
    如果第一个范围中的点位于第二个范围中的点之后,返回 1。来看下面的例子。
    在这里插入图片描述
    在这个例子中,两个范围的起点实际上是相同的,因为它们的起点都是由 selectNodeContents() 方法设置的默认值来指定的。因此,第一次比较返回 0。但是,range2 的终点由于调用 setEndBefore() 已经改变了,结果是 range1 的终点位于 range2 的终点后面(见图 12-10),因此第二次比较返回 1。
    在这里插入图片描述
7、复制 DOM 范围

可以使用 cloneRange()方法复制范围。这个方法会创建调用它的范围的一个副本

 var newRange = range.cloneRange();

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

8、清理DOM范围

在使用完范围之后,最好是调用 detach()方法,以便从创建范围的文档中分离出该范围。调用 detach()之后,就可以放心地解除对范围的引用,从而让垃圾回收机制回收其内存了。来看下面的例子。

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

在使用范围的最后再执行这两个步骤是我们推荐的方式。一旦分离范围,就不能再恢复使用了。

二、IE8 及更早版本中的范围

1、文本范围
  1. 虽然 IE9 支持 DOM 范围,但 IE8 及之前版本不支持 DOM 范围。不过,IE8 及早期版本支持一种类似的概念,即文本范围(text range)
  2. 文本范围是 IE 专有的特性,其他浏览器都不支持。顾名思义,文本范围处理的主要是文本(不一定是 DOM 节点)。
  3. createTextRange()方法:通过<body><button><input><textarea> 等这几个元素,可以调用 createTextRange()方法来创建文本范围。以下是一个例子:
var range = document.body.createTextRange();

像这样通过 document 创建的范围可以在页面中的任何地方使用(通过其他元素创建的范围则只能在相应的元素中使用)。与 DOM 范围类似,使用 IE 文本范围的方式也有很多种。

2、用 IE 范围实现简单的选择
  1. findText()方法选择页面中某一区域的最简单方式,就是使用范围的 findText()方法。这个方法会找到第一次出现的给定文本,并将范围移过来以环绕该文本。如果没有找到文本,这个方法返回 false;否则返回 true。同样,仍然以下面的 HTML 代码为例。
    <p id="p1"><b>Hello</b> world!</p>
    
  • 要选择"Hello",可以使用下列代码。
var range = document.body.createTextRange();
var found = range.findText("Hello");
//在执行完第二行代码之后,文本"Hello"就被包围在范围之内了。
  • 在执行完第二行代码之后,文本"Hello"就被包围在范围之内了。为此,可以检查范围的 text 属性来确认(这个属性返回范围中包含的文本),或者也可以检查 findText()的返回值——在找到了文本的情况下返回值为 true。例如:
alert(found); //true 
alert(range.text); //"Hello"
  • 还可以为 findText()传入另一个参数,即一个表示向哪个方向继续搜索的数值。负值表示应该从当前位置向后搜索,而正值表示应该从当前位置向前搜索。因此,要查找文档中前两个"Hello"的实例, 应该使用下列代码。
var found = range.findText("Hello");
var foundAgain = range.findText("Hello", 1);//继续往下搜索
  1. moveToElementText()方法:IE 中与 DOM 中的 selectNode()方法最接近的方法是 moveToElementText(),这个方法接受一个DOM 元素,并选择该元素的所有文本,包括 HTML 标签。下面是一个例子。
 var range = document.body.createTextRange();
var p1 = document.getElementById("p1");
range.moveToElementText(p1);
  • htmlText 属性: 在文本范围中包含 HTML 的情况下,可以使用 htmlText 属性取得范围的全部内容,包括 HTML 和文本,如下面的例子所示。
alert(range.htmlText);
  1. parentElement()方法:IE 的范围没有任何属性可以随着范围选区的变化而动态更新。不过,其 parentElement()方法倒是与 DOM 的 commonAncestorContainer 属性类似
var ancestor = range.parentElement(); 

这样得到的父元素始终都可以反映文本选区的父节点

3、使用 IE 范围实现复杂的选择
  1. 在 IE 中创建复杂范围的方法,就是以特定的增量向四周移动范围。为此,IE 提供了 4 个方法move()moveStart()moveEnd()expand()。这些方法都接受两个参数:移动单位和移动单位的数量。其中,移动单位是下列一种字符串值。
    在这里插入图片描述
  2. 通过 moveStart()方法可以移动范围的起点,通过 moveEnd()方法可以移动范围的终点,移动的幅度由单位数量指定,如下面的例子所示。
range.moveStart("word", 2); //起点移动 2 个单词 
range.moveEnd("character", 1); //终点移动 1 个字符
  1. 使用 expand()方法可以将范围规范化。换句话说,expand()方法的作用是将任何部分选择的文本全部选中。例如,当前选择的是一个单词中间的两个字符,调用 expand(“word”)可以将整个单词都包含在范围之内
  2. move()方法首先会折叠当前范围(让起点和终点相等),然后再将范围移动指定的单位数量,如下面的例子所示。
range.move("character", 5); //移动 5 个字符

调用 move()之后,范围的起点和终点相同,因此必须再使用 moveStart()或 moveEnd()创建新的选区。(疑问:那使用折叠的目的是什么呢?是为了整个范围一起移动吗?折叠的本质就是让起点和终点相等吗?)

4、操作 IE 范围中的内容

在 IE 中操作范围中的内容可以使用 text 属性pasteHTML()方法

  1. 如前所述,通过 text 属性可以取得范围中的内容文本;但是,也可以通过这个属性设置范围中的内容文本。来看一个例子。

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

    注意,在设置 text 属性的情况下,HTML 标签保持不变

  2. 要向范围中插入 HTML 代码,就得使用 pasteHTML()方法,如下面的例子所示。

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

    执行这些代码后,会得到如下 HTML。

    // 注意:范围中存的是引用,修改范围的内容会直接修改html
    <p id="p1"><b><em>Howdy</em></b> world!</p>
    

不过,在范围中包含 HTML 代码时,不应该使用 pasteHTML(),因为这样很容易导致不可预料的 结果——很可能是格式不正确的 HTML。

5、折叠IE范围
  • collapse()方法:IE 为范围提供的 collapse()方法与相应的 DOM 方法用法一样:传入 true 把范围折叠到起点, 传入 false 把范围折叠到终点。例如:
range.collapse(true); //折叠到起点
  • boundingWidth 属性:可惜的是,没有对应的 collapsed 属性让我们知道范围是否已经折叠完毕。为此,必须使用 boundingWidth 属性,该属性返回范围的宽度(以像素为单位)。如果 boundingWidth 属性等于 0, 就说明范围已经折叠了:
var isCollapsed = (range.boundingWidth == 0);

此外,还有 boundingHeight、boundingLeft 和 boundingTop 等属性,虽然它们都不像 boundingWidth 那么有用,但也可以提供一些有关范围位置的信息。

6、比较 IE 范围
  1. compareEndPoints()方法:IE 中的 compareEndPoints()方法与 DOM 范围的 compareBoundaryPoints()方法类似。
  • 这个方法接受两个参数:比较的类型和要比较的范围比较类型的取值范围是下列几个字符串值: “StartToStart” 、 “StartToEnd” 、 “EndToEnd” 和 “EndToStart”。这几种比较类型与比较 DOM 范围时使用的几个值是相同的。
  • 同样与 DOM 类似的是,compareEndPoints()方法也会按照相同的规则返回值,即如果第一个范围的边界位于第二个范围的边界前面,返回-1;如果二者边界相同,返回 0;如果第一个范围的边界位 于第二个范围的边界后面,返回 1。仍以前面的 Hello World 代码为例,下列代码将创建两个范围,一个 选择"Hello world!"(包括<b>标签),另一个选择"Hello"。
  1. IE 中还有两个方法,也是用于比较范围的:isEqual()用于确定两个范围是否相等,inRange() 用于确定一个范围是否包含另一个范围。下面是相应的示例。
    在这里插入图片描述
7、复制 IE 范围

duplicate()方法:在 IE 中使用可以复制文本范围,结果会创建原范围的一个副本,如下面的例子 所示。

var newRange = range.dupicate();

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
点击父级子级展开,或者收起 <script type=text/javascript><!-- var LastLeftID = ""; function menuFix() { var obj = document.getElementById("nav").getElementsByTagName("li"); for (var i=0; i<obj.length; i++) { obj[i].onmouseover=function() { this.className+=(this.className.length>0? " ": "") + "sfhover"; } obj[i].onMouseDown=function() { this.className+=(this.className.length>0? " ": "") + "sfhover"; } obj[i].onMouseUp=function() { this.className+=(this.className.length>0? " ": "") + "sfhover"; } obj[i].onmouseout=function() { this.className=this.className.replace(new RegExp("( ?|^)sfhover\\b"), ""); } } } function DoMenu(emid) { var obj = document.getElementById(emid); obj.className = (obj.className.toLowerCase() == "expanded"?"collapsed":"expanded"); if((LastLeftID!="")&&(emid!=LastLeftID)) //关闭上一个Menu { document.getElementById(LastLeftID).className = "collapsed"; } LastLeftID = emid; } function GetMenuID() { var MenuID=""; var _paramStr = new String(window.location.href); var _sharpPos = _paramStr.indexOf("#"); if (_sharpPos >= 0 && _sharpPos < _paramStr.length - 1) { _paramStr = _paramStr.substring(_sharpPos + 1, _paramStr.length); } else { _paramStr = ""; } if (_paramStr.length > 0) { var _paramArr = _paramStr.split("&"); if (_paramArr.length>0) { var _paramKeyVal = _paramArr[0].split("="); if (_paramKeyVal.length>0) { MenuID = _paramKeyVal[1]; } } /* if (_paramArr.length>0) { var _arr = new Array(_paramArr.length); } //取所有#后面的,菜单只需用到Menu //for (var i = 0; i < _paramArr.length; i++) { var _paramKeyVal = _paramArr[i].split('='); if (_paramKeyVal.length>0) { _arr[_paramKeyVal[0]] = _paramKeyVal[1]; } } */ } if(MenuID!="") { DoMenu(MenuID) } } GetMenuID(); //*这两个function的顺序要注意一下,不然在Firefox里GetMenuID()不起效果 menuFix(); --></script>

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值