AbstractRange

Baseline 已廣泛支援

此特性已得到良好支援,可在多種裝置和瀏覽器版本上使用。自 2021 年 4 月起,所有瀏覽器均已支援此特性。

AbstractRange 抽象介面是定義所有 DOM 範圍型別的基礎類。範圍(range)是一個物件,用於指示文件中內容部分的起始點和結束點。

注意: 作為一個抽象介面,你不能直接例項化 AbstractRange 型別的物件。你應該使用 RangeStaticRange 介面。要了解這兩個介面的區別,以及如何根據你的需求選擇合適的介面,請查閱每個介面的文件。

例項屬性

collapsed 只讀

一個布林值,如果範圍是摺疊的(collapsed),則為 true。摺疊的範圍是指其開始位置和結束位置相同的範圍,從而得到一個零字元長度的範圍。

endContainer 只讀

endOffset 屬性指定的範圍結束點所在的 Node 物件。

endOffset 只讀

一個整數值,表示範圍物件所表示的範圍的最後一個字元,相對於節點內容的開始位置的偏移量(以字元為單位)。此值必須小於 endContainer 節點 的長度。

startContainer 只讀

startOffset 屬性指定的範圍開始點所在的 DOM Node

startOffset 只讀

一個整數值,表示範圍物件所引用的內容的第一個字元,相對於節點內容的開始位置的偏移量(以字元為單位)。此值必須小於 startContainer 中指示的節點 的長度。

例項方法

AbstractRange 介面不提供任何方法。

用法說明

範圍型別

document 中所有內容範圍都使用基於 AbstractRange 的介面例項來描述。有以下兩種介面:

Range

Range 介面存在已久,最近才被重新定義為基於 AbstractRange,以滿足定義其他形式範圍資料的需求。Range 提供了允許你修改範圍端點的方法,以及用於比較範圍、檢測範圍交叉等的方法。

StaticRange

StaticRange 是一個基礎範圍,一旦建立就無法更改。具體來說,隨著節點樹的變異和改變,該範圍保持不變。這在你只需要一次性使用某個範圍時非常有用,因為它避免了更復雜的 Range 介面帶來的效能和資源影響。

元素的內容

在嘗試訪問元素的內容時,請記住,元素本身是一個節點,它內部的任何文字也是一個節點。為了在元素的文字內設定範圍端點,請確保找到元素內部的文字節點。

js
const startElem = document.querySelector("p");
const endElem = startElem.querySelector("span");
const range = document.createRange();

range.setStart(startElem, 0);
range.setEnd(endElem, endElem.childNodes[0].length / 2);
const contents = range.cloneContents();

document.body.appendChild(contents);

此示例建立一個新的範圍 range,並將其起始點設定為第一個元素的第三個子節點。結束點被設定為 span 的第一個子節點的中間位置,然後使用該範圍複製範圍的內容。

範圍與 DOM 的層級結構

為了能夠跨越零個或多個節點邊界來定義文件中的字元範圍,並且儘可能地對 DOM 更改具有彈性,你不能在 HTML 中直接指定第一個和最後一個字元的偏移量。這有幾個很好的原因。

首先,在頁面載入完成後,瀏覽器不再以 HTML 的形式思考。一旦載入完成,頁面就是一個 DOM Node 物件樹,因此你需要使用節點和節點內的位置來指定範圍的開始和結束位置。

其次,為了儘可能地支援 DOM 樹的可變性,你需要一種方式來表示相對於樹中節點的位置,而不是相對於整個文件的全域性位置。透過將文件中的點定義為給定節點內的偏移量,即使節點被新增到、從 DOM 樹中移除或在 DOM 樹中移動(在合理範圍內),這些位置也能與內容保持一致。當然存在一些明顯的限制(例如,如果一個節點被移動到範圍的結束點之後,或者節點的內容被嚴重修改),但這比什麼都沒有要好得多。

第三,使用節點相對位置來定義開始和結束位置通常更容易獲得高效能。使用者代理(瀏覽器)不需要遍歷 DOM 來確定全域性偏移量指的是什麼,而是可以直接定位到起始位置指示的節點,然後從那裡開始,向前移動直到到達結束節點中給定的偏移量。

為了說明這一點,請考慮下面的 HTML

html
<div class="container">
  <div class="header">
    <img src="" class="sitelogo" />
    <h1>The Ultimate Website</h1>
  </div>
  <article>
    <section class="entry" id="entry1">
      <h2>Section 1: An interesting thing…</h2>
      <p>A <em>very</em> interesting thing happened on the way to the forum…</p>
      <aside class="callout">
        <h2>Aside</h2>
        <p>An interesting aside to share with you…</p>
      </aside>
    </section>
  </article>
  <pre id="log"></pre>
</div>

載入 HTML 並構建文件的 DOM 表示後,生成的 DOM 樹如下所示:

Diagram of the DOM for a simple web page

在此圖中,代表 HTML 元素的節點顯示為綠色。它們下方的每一行顯示了 DOM 樹中下一層級的深度。藍色節點是文字節點,包含螢幕上顯示的文字。每個元素的子節點都連結在其下方,可能透過元素包含其他元素和文字節點,從而在下方形成一系列分支。

如果你想建立一個包含 <p> 元素(其內容是 "A <em>very</em> interesting thing happened on the way to the forum…")的範圍,可以這樣做:

js
const pRange = document.createRange();
pRange.selectNodeContents(document.querySelector("#entry1 p"));

由於我們希望選擇 <p> 元素及其子元素的全部內容,所以這樣做是完美的。

如果我們希望複製 <section> 的標題(一個 h2 元素)中的文字“An interesting thing…”一直到下面段落中 <em> 中的字母“ve”結束,則以下程式碼會起作用:

js
const range = document.createRange();
const startNode = document.querySelector("section h2").childNodes[0];
range.setStart(startNode, 11);

const endNode = document.querySelector("#entry1 p em").childNodes[0];
range.setEnd(endNode, 2);

const fragment = range.cloneContents();

這裡出現了一個有趣的問題——我們正在捕獲來自 DOM 層級中不同位置的多個節點的內容,並且只捕獲其中一部分。結果應該是什麼樣的?

事實證明,DOM 規範恰好解決了這個問題。例如,在這種情況下,我們正在呼叫範圍的 cloneContents() 方法來建立一個新的 DocumentFragment 物件,該物件提供了一個 DOM 子樹,複製了指定範圍的內容。為了做到這一點,cloneContents() 會構建所有必要節點來保留指定範圍的結構,但不會多餘。

在此示例中,指定範圍的開始點位於 section 標題下方的文字節點中,這意味著新的 DocumentFragment 需要包含一個 h2,並在其下方包含一個文字節點。

範圍的結束點位於 <p> 元素下方,因此它需要包含在新片段中。包含“A”的文字節點也需要,因為它包含在範圍內。最後,一個 <em> 元素及其下方的文字節點也將被新增到 <p> 元素下方。

文字節點的具體內容由呼叫 setStart()setEnd() 時給定的文字節點內的偏移量決定。由於標題文字的偏移量為 11,因此該節點將包含“An interesting thing…”。同樣,根據對結束節點前兩個字元的要求,最後一個文字節點將包含“ve”。

生成的文件片段如下所示:

A DocumentFragment representing the cloned content

特別要注意的是,此片段的內容都位於其內頂層節點共享公共父節點的下方。父節點 <section> 不需要複製克隆的內容,因此未包含在內。

示例

考慮以下簡單的 HTML 片段:

html
<p><strong>This</strong> is a paragraph.</p>

假設使用 Range 來提取其中的單詞“paragraph”。實現此目的的程式碼如下所示:

js
const paraNode = document.querySelector("p");
const paraTextNode = paraNode.childNodes[1];

const range = document.createRange();
range.setStart(paraTextNode, 6);
range.setEnd(paraTextNode, paraTextNode.length - 1);

const fragment = range.cloneContents();
document.body.appendChild(fragment);

首先,我們獲取對段落節點本身以及段落內第二個子節點的引用。第一個子節點是 <strong> 元素。第二個子節點是文字節點“ is a paragraph.”。

有了文字節點的引用後,我們透過在 Document 本身上呼叫 createRange() 來建立一個新的 Range 物件。我們將範圍的起始位置設定為文字節點字串的第六個字元,結束位置設定為文字節點字串的長度減一。這會將範圍設定為包含單詞“paragraph”。

然後,我們透過呼叫 Range 上的 cloneContents() 來完成操作,建立一個新的 DocumentFragment 物件,該物件包含範圍所覆蓋的文件部分。之後,我們使用 appendChild() 將該片段新增到文件的 body 末尾,該 body 透過 document.body 獲取。

結果如下所示

規範

規範
DOM
# interface-abstractrange

瀏覽器相容性