AbstractRange
AbstractRange
抽象接口是定义所有 DOM 范围类型的基础类。范围(range)是一个对象,用于指示文档中内容部分的起始点和结束点。
注意: 作为一个抽象接口,你不能直接实例化 AbstractRange
类型的对象。你应该使用 Range
或 StaticRange
接口。要了解这两个接口的区别,以及如何根据你的需求选择合适的接口,请查阅每个接口的文档。
实例属性
collapsed
只读-
一个布尔值,如果范围是折叠的(collapsed),则为
true
。折叠的范围是指其开始位置和结束位置相同的范围,从而得到一个零字符长度的范围。 endContainer
只读-
endOffset
属性指定的范围结束点所在的Node
对象。 endOffset
只读-
一个整数值,表示范围对象所表示的范围的最后一个字符,相对于节点内容的开始位置的偏移量(以字符为单位)。此值必须小于
endContainer
节点 的长度。 startContainer
只读-
startOffset
属性指定的范围开始点所在的 DOMNode
。 startOffset
只读-
一个整数值,表示范围对象所引用的内容的第一个字符,相对于节点内容的开始位置的偏移量(以字符为单位)。此值必须小于
startContainer
中指示的节点 的长度。
实例方法
AbstractRange
接口不提供任何方法。
用法说明
范围类型
document
中所有内容范围都使用基于 AbstractRange
的接口实例来描述。有以下两种接口:
Range
-
Range
接口存在已久,最近才被重新定义为基于AbstractRange
,以满足定义其他形式范围数据的需求。Range
提供了允许你修改范围端点的方法,以及用于比较范围、检测范围交叉等的方法。 StaticRange
-
StaticRange
是一个基础范围,一旦创建就无法更改。具体来说,随着节点树的变异和改变,该范围保持不变。这在你只需要一次性使用某个范围时非常有用,因为它避免了更复杂的Range
接口带来的性能和资源影响。
元素的內容
在尝试访问元素的内容时,请记住,元素本身是一个节点,它内部的任何文本也是一个节点。为了在元素的文本内设置范围端点,请确保找到元素内部的文本节点。
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
<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 树如下所示:
在此图中,代表 HTML 元素的节点显示为绿色。它们下方的每一行显示了 DOM 树中下一层级的深度。蓝色节点是文本节点,包含屏幕上显示的文本。每个元素的子节点都链接在其下方,可能通过元素包含其他元素和文本节点,从而在下方形成一系列分支。
如果你想创建一个包含 <p>
元素(其内容是 "A <em>very</em> interesting thing happened on the way to the forum…"
)的范围,可以这样做:
const pRange = document.createRange();
pRange.selectNodeContents(document.querySelector("#entry1 p"));
由于我们希望选择 <p>
元素及其子元素的全部内容,所以这样做是完美的。
如果我们希望复制 <section>
的标题(一个 h2 元素)中的文本“An interesting thing…”一直到下面段落中 <em>
中的字母“ve”结束,则以下代码会起作用:
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”。
生成的文档片段如下所示:
特别要注意的是,此片段的内容都位于其内顶层节点共享公共父节点的下方。父节点 <section>
不需要复制克隆的内容,因此未包含在内。
示例
考虑以下简单的 HTML 片段:
<p><strong>This</strong> is a paragraph.</p>
假设使用 Range
来提取其中的单词“paragraph”。实现此目的的代码如下所示:
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 |
浏览器兼容性
加载中…