Javascript标准DOM Range操作全集

  2级DOM定义了一个createRange()方法,如果是按照DOM此标准的浏览器(IE并不是支持此标准的,但是IE里的属性或方法却远比标准中定义的多得多),它属于document对象,所以创建一个range对象要这样做:

  var oRange = document.createRange();

  如果你要检测你的浏览器是否支持此标准Range对象,可以用hasFeature()方法来检测:

  var supportsDOMRanges = document.implementation.hasFeature("Range", "2.0");

  if (supportsDOMRange) {

  var oRange = document.createRange();

  //range code here

  }

  Range对象进行简单的选择

  最简单用Range进行选择,用selectNode()或者selectNodeContents()方法,这两个方法只有一个接收参数,一个DOM节点。

  selectNode()方法选择全部节点,包括它的孩子,而selectNodeContents()选择的节点只是它的孩子。如

<p id="p1"><b>Hello</b> World</p>

  <script>

  var oRange1 = document.createRange();

  var oRange2 = document.createRange();

  var oP1 = document.getElementById("p1");

  oRange1.selectNode(oP1);

  oRange2.selectNodeContents(oP1);

  </script>

oRange1和oRange2包含上面所说的两种方法,但是看了下面的示图相信你能很快明白这两个方法的区别:

Javascript标准DOM Range操作全集

  当你创建了一个Range对象时,Range实例就会有以下的属性:

  startContainer — 返回range对象从何开始的节点对象(父节点的第一个节点)

  startOffset — 返回Range开始的偏移量(offset),如果startContainer是一个文本节点,注释节点,或者是CDATA节点,这个属性返回文本的偏移量,否则返回第一个节点的索引。

  endCOntainer — 返回Range对象最后一个节点对象(父节点的最后一个节点)

  endOffset — 返回Range结束时的偏移量(offset)特性与startOffset相同。

  commonAncestorContainer — 返回第一个包含该Range对象的节点。

  注:这些属性均为只读属性(read-only),startOffset和endOffset将在下文中有较详细的解释。

  下面这段代码将说明这些属性,请在Mozilla firefox里运行(支持此标准的浏览器——DOM2级,IE里将无效):

<html>

  <head>

  <title>DOM Range Example</title>

  <script type="text/javascript">

  function useRanges() {

  var oRange1 = document.createRange();

  var oRange2 = document.createRange();

  var oP1 = document.getElementById("p1");

  oRange1.selectNode(oP1);

  oRange2.selectNodeContents(oP1);

  document.getElementById("txtStartContainer1").value

  = oRange1.startContainer.tagName;

  document.getElementById("txtStartOffset1").value =

  oRange1.startOffset;

  document.getElementById("txtEndContainer1").value =

  oRange1.endContainer.tagName;

  document.getElementById("txtEndOffset1").value =

  oRange1.endOffset;

  document.getElementById("txtCommonAncestor1").value =

  oRange1.commonAncestorContainer.tagName;

  document.getElementById("txtStartContainer2").value =

  oRange2.startContainer.tagName;

  document.getElementById("txtStartOffset2").value =

  oRange2.startOffset;

  document.getElementById("txtEndContainer2").value =

  oRange2.endContainer.tagName;

  document.getElementById("txtEndOffset2").value =

  oRange2.endOffset;

  document.getElementById("txtCommonAncestor2").value =

  oRange2.commonAncestorContainer.tagName;

  }

  </script>

  </head>

  <body><p id="p1"><b>Hello</b> World</p>

  <input type="button" value="Use Ranges" onclick="useRanges()" />

  <table border="0">

  <tr>

  <td>

  <fieldset>

  <legend>oRange1</legend>

  Start Container:

  <input type="text" id="txtStartContainer1" /><br />

  Start Offset:

  <input type="text" id="txtStartOffset1" /><br />

  End Container:

  <input type="text" id="txtEndContainer1" /><br />

  End Offset:

  <input type="text" id="txtEndOffset1" /><br />

  Common Ancestor:

  <input type="text" id="txtCommonAncestor1" /><br />

  </fieldset>

  </td>

  <td>

  <fieldset>

  <legend>oRange2</legend>

  Start Container:

  <input type="text" id="txtStartContainer2" /><br />

  Start Offset:

  <input type="text" id="txtStartOffset2" /><br />

  End Container:

  <input type="text" id="txtEndContainer2" /><br />

  End Offset:

  <input type="text" id="txtEndOffset2" /><br />

  Common Ancestor:

  <input type="text" id="txtCommonAncestor2" /><br />

  </fieldset>

  </td>

  </tr>

  </table>

  </body>

  </html>

上面的代码将不作注释了,有什么问题,在评论中留言。

  Range中还有一些其它的方法:

  setStartBefore(node) — 设置Range的相对于node节点的起始位置

  setStartAfter(node) — 同上

  setEndBefore — 设置Range的相对于node节点的结束位置

  setEndAfter — 同上

  复杂的DOM Range

  建立复杂的DOM range需要使用setStart()和setEnd()两个方法,这两个方法有两个参数:一个是一个节点(node)引用和一个偏移(offset)。

  setStart方法节点的引用是startContainer,偏移则是startOffset;

  setEnd()方法时,节点引用为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里(code <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()方法中的偏移为3,原因同上,需要注意的是World前面有一个空格,空格也是占位置的。如图:

Javascript标准DOM Range操作全集

  注意:

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

  用DOM Range做一些操作

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

  比如以面这段Range

Javascript标准DOM Range操作全集

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

<p><b>He</b><b>llo</b> World</p>
也就是自动加上了开始标签<b>,使得整个Range变为<b>llo</b> Wo,fragment的示意图为:

Javascript标准DOM Range操作全集

  当此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(),但是不是删除,而是克隆。

  从Range中插入一些数据

  前一节的几个方法解决了如何移除range中所选中的fragment。现在说明如何添加内容到Range中。

  insertNode()方法可以插入一个节点到Range中。假如我想把以下的节点插如Range中,将如何操作呢?

  <span style="color: red">Inserted text</span>

  看下面的代码:

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

  var oSpan = document.createElement("span");

  oSpan.style.color = "red";

  oSpan.appendChild(document.createTextNode("Inserted text"));

  oRange.setStart(oHello, 2);

  oRange.setEnd(oWorld, 3);

  oRange.insertNode(oSpan);

  </script>

  那么原来的HTML将会变成这样:

  <p id="p1"><b>He<span style="color: red">Inserted text</span>llo</b> World</p>

  surroundContents()的参数为一个node,它将这个node加入到Range,下面看这个示例。

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

  var oSpan = document.createElement("span");

  oSpan.style.backgroundColor = "yellow";

  oRange.setStart(oHello, 2);

  oRange.setEnd(oWorld, 3);

  oRange.surroundContents(oSpan);

  </script>

  在oRange选取的范围内有一个我们新生成的节点span,因此选取的Range的背景变成了黄色。

  collapse()方法:

  collapse()方法只有一个布尔型的参数,该参数为可选的,也就是说,可以有,也可以没有,默认为false。

  true时折叠到Range边界的首部,为false时折叠到Range尾部。即

Javascript标准DOM Range操作全集

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

  oRange.collapse(true);

  </script>

  如果你想知道该Range是否已经折叠,可以用collapsed属性来得到true或者false。看下面的例子。

<p id="p1">Paragraph 1</p><p id="p2">Paragraph 2</p>

  <script>

  var oP1 = document.getElementById("p1");

  var oP2 = document.getElementById("p2");

  var oRange = document.createRange();

  oRange.setStartAfter(oP1);

  oRange.setStartBefore(oP2);

  alert(oRange.collapsed); //outputs "true"

  </script>

上面的代码输为true。虽然我们没有用collapse方法,但是由于我们的Range设置开始为1末端到p2的首端,没有任何元素。即</p>(Range开始)(Range结束)<p id="p2">,所以显示的是true。

  Range边界的比较

  compareBoundaryPoints()方法,语法形式如下:

  compare = comparerange.compareBoundaryPoints(how,sourceRange)

  参数含义:

  compare —— 返回1, 0, -1.(0为相等,1为时,comparerange在sourceRange之后,-1为comparerange在sourceRange之前)

  how —— 为Range常数:END_TO_END|END_TO_START|START_TO_END|START_TO_START

  sourceRange —— 一个Range对象的边界。

  看下面的例子:

<p id="p1"><b>Hello</b> World</p>

  <script>

  var oRange1 = document.createRange();

  var oRange2 = document.createRange();

  var oP1 = document.getElementById("p1");

  oRange1.selectNodeContents(oP1);

  oRange2.selectNodeContents(oP1);

  oRange2.setEndBefore(oP1.lastChild);

  alert(oRange1.compareBoundaryPoints(Range.START_TO_START, oRange2));

  //outputs 0

  alert(oRange1.compareBoundaryPoints(Range.END_TO_END, oRange2));

  //outputs 1;

  </script>

下图为这两个Range的示意图,结合代码和上面的说明,可以清晰的分析出结果了。

Javascript标准DOM Range操作全集

  克隆(clone)Range

  这个操作很简单,只需要一句语句即可:

var oNewRange = oRange.cloneRange();

  cloneRange()方法将返回一个当前Range的副本,当然,它也是Range对象。

  清除Range所占的系统资源

  当你创建了Range对象最好用detach()方法来清除它所占的系统资源。虽然不清除,GC(垃圾收集器)也会将其收集,但用detach()释放是一个好习惯。语法为:

  

oRange.detach();

  下面一个示例在Mozilla中,利用Range可以模拟出IE中的element.insertAdjacentHTML()方法,

if (browser.isMozilla) {

  HTMLElement.prototype.insertAdjacentHTML = function (sWhere, sHTML) {

  var df; var r = this.ownerDocument.createRange();

  switch (String(sWhere).toLowerCase()) {

  case "beforebegin":

  r.setStartBefore(this);

  df = r.createContextualFragment(sHTML);

  this.parentNode.insertBefore(df, this);

  break;

  case "afterbegin":

  r.selectNodeContents(this);

  r.collapse(true);

  df = r.createContextualFragment(sHTML);

  this.insertBefore(df, this.firstChild);

  break;

  case "beforeend":

  r.selectNodeContents(this);

  r.collapse(false);

  df = r.createContextualFragment(sHTML);

  this.appendChild(df);

  break;

  case "afterend":

  r.setStartAfter(this);

  df = r.createContextualFragment(sHTML);

  this.parentNode.insertBefore(df, this.nextSibling);

  break;

  }

  };

  }

参考文档:

  

  Professional JavaScript for Web Developers(Wrox)

  Mozilla Develop Center Document