AbstractRange

AbstractRange 抽象接口是所有 DOM 范围类型定义的基础类。范围 是一个对象,它指示文档内内容节的起点和终点。

注意: 作为一个抽象接口,你不会直接实例化一个 AbstractRange 类型的对象。相反,你会使用 RangeStaticRange 接口。要了解这两个接口之间的区别,以及如何选择适合你需求的接口,请参阅每个接口的文档。

实例属性

collapsed 只读

一个布尔值,如果范围是 折叠 的,则为 true。折叠范围是指其起始位置和结束位置相同的范围,结果是一个零字符长度的范围。

endContainer 只读

Node 对象,其中包含范围的结束位置(由 endOffset 属性指定)。

endOffset 只读

一个整数,表示从节点内容开头到范围对象表示的范围的最后一个字符的偏移量(以字符为单位)。该值必须小于 endContainer 节点的长度。

startContainer 只读

DOM Node,其中包含范围的起始位置(由 startOffset 属性指定)。

startOffset 只读

一个整数,表示从节点内容开头到范围对象引用的内容的第一个字符的偏移量(以字符为单位)。该值必须小于 startContainer 指示的节点的长度。

实例方法

AbstractRange 接口不提供任何方法。

使用说明

范围类型

文档中所有内容范围都是使用基于 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,并将其起始位置设置为类为 elementclass 的第一个元素的第三个子节点。结束位置设置为跨度第一个子节点的中间位置,然后使用该范围复制范围的内容。

范围和 DOM 的层次结构

为了以一种能够跨越零个或多个节点边界,并且尽可能地对 DOM 更改具有弹性的方式来定义文档中的字符范围,你不能指定 HTML 中第一个和最后一个字符的偏移量。这样做的原因有很多。

首先,在页面加载后,浏览器不会以 HTML 的方式思考。一旦页面加载完毕,页面就变成了 DOM Node 对象的树,因此你需要以节点和节点内位置的方式来指定范围的起始和结束位置。

其次,为了尽可能地支持 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 树中下一层深度。蓝色节点是文本节点,包含屏幕上显示的文本。每个元素的内容都链接在其下方,在树中可能产生一系列分支,因为元素包含其他元素和文本节点。

如果你想创建一个范围,该范围包含内容为 "A <em>very</em> interesting thing happened on the way to the forum…"<p> 元素的内容,你可以这样做

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() 将构造所有必要的节点以保留所指示范围的结构,但不会构造超出必要范围的节点。

在此示例中,指定范围的起始位置位于节标题下面的文本节点内,这意味着新的 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 从中提取“段落”一词。执行此操作的代码如下所示

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> 元素。第二个子节点是文本节点“是一个段落”。

有了文本节点引用,我们通过在Document本身调用 createRange() 来创建一个新的Range对象。我们将范围的起始位置设置为文本节点字符串的第六个字符,结束位置设置为文本节点字符串的长度减一。这将范围设置为包含“段落”一词。

然后,我们通过在Range上调用 cloneContents() 来完成,以创建一个新的 DocumentFragment 对象,其中包含范围内包含的文档部分。之后,我们使用 appendChild() 将该片段添加到文档正文的末尾,该正文从 document.body 获取。

结果如下所示

规范

规范
DOM 标准
# interface-abstractrange

浏览器兼容性

BCD 表格仅在浏览器中加载