JavaScript 标准 DOM Range 操作

发布于 2021-11-01 12:57:48 字数 13334 浏览 1200 评论 0

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包含上面所说的两种方法,但是看了下面的示图相信你能很快明白这两个方法的区别。

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里

<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前面有一个空格,空格也是占位置的。

注意

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

用 DOM Range 做一些操作

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

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

<p><b>He</b><b>llo</b> World</p>

也就是自动加上了开始标签b,使得整个Range变为

<b>llo</b> Wo

当此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 尾部。

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

克隆(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;
   }
 };
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

葬花如无物

暂无简介

0 文章
0 评论
520 人气
更多

推荐作者

醉城メ夜风

文章 0 评论 0

远昼

文章 0 评论 0

平生欢

文章 0 评论 0

微凉

文章 0 评论 0

Honwey

文章 0 评论 0

qq_ikhFfg

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文