Javascript標準DOM Range操作

來源:互聯網
上載者:User

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執行個體就會有以下的屬性:
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前面有一個空格,空格也是佔位置的。

注意:
(Mozilla DOM Range bug #135928)在 Mozilla低版本瀏覽器 執行此Range方法時,如果setStart()和setEnd()都指向同一個文本節點會出現異常

用DOM Range做一些操作
當建立一個Range對象時,在Range裡的所有對象之上,已經建立了一個文檔的fragment節點。在這之前,Range對象必須合格證你選擇的這段Range是一個well-formed(格式良好)。
比如以面這段Range

很明顯的,在這裡,並不是一個well-formed,上面說過了,當建立一個Range時,會自動產生一個fragment,在這裡,framgment自動動態添加一些元素,以保證Range的正確性:

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

也就是自動加上了開始標籤<b>,使得整個Range變為<b>llo</b> Wo,fragment的為:

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

為這兩個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;
 }
 };
}
相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.