在本章中,我们将介绍文档中的选择以及在表单字段(如 <input>)中的选择。
JavaScript 可以访问现有的选择,选择/取消全部或部分 DOM 节点的选择,从文档中删除所选部分,将其包装到一个标签(tag)中,等。
你可以在本章末尾的“总结”部分找到一些常见的使用方式。可能就已经满足了你当前的需求,但如果你阅读全文,将会有更多收获。
底层的(underlying)Range 和 Selection 对象很容易掌握,因此,你不需要任何诀窍便可以使用它们做你想要做的事儿。
范围
选择的基本概念是 Range:本质上是一对“边界点”:范围起点和范围终点。
在没有任何参数的情况下,创建一个 Range 对象:
let range = new Range();
然后,我们可以使用 range.setStart(node, offset) 和 range.setEnd(node, offset) 来设置选择边界。
正如你可能猜到的那样,我们将进一步使用 Range 对象进行选择,但首先让我们创建一些这样的对象。
选择部分文本
有趣的是,这两种方法中的第一个参数 node 都可以是文本节点或元素节点,而第二个参数的含义依赖于此。
如果 node 是一个文本节点,那么 offset 则必须是其文本中的位置。
例如,对于给定的 <p>Hello</p>,我们可以像下面这样创建一个包含字母 “ll” 的范围:
<p id="p">Hello</p>
<script>
let range = new Range();
range.setStart(p.firstChild, 2);
range.setEnd(p.firstChild, 4);
// 对 range 进行 toString 处理,range 则会把其包含的内容以文本的形式返回
console.log(range); // ll
</script>
在这里,我们获取 <p> 的第一个子节点(即文本节点)并指定其中的文本位置:
选择元素节点
或者,如果 node 是一个元素节点,那么 offset 则必须是子元素的编号。
这对于创建包含整个节点的范围很方便,而不是在其文本中的某处停止。
例如,我们有一个更复杂的文档片段:
<p id="p">Example: <i>italic</i> and <b>bold</b></p>
这是它的 DOM 结构,包含元素和文本节点:
让我们为 "Example: <i>italic</i>" 设置一个范围。
正如我们所看到的,这个短语正好由 <p> 的索引为 0 和 1 的两个子元素组成。
-
起点以
<p>作为父节点node,0作为偏移量。因此,我们可以将其设置为
range.setStart(p, 0)。 -
终点也是以
<p>作为父节点node,但以2作为偏移量(它指定最大范围,但不包括offset)。因此,我们可以将其设置为
range.setEnd(p, 2)。
示例如下,如果你运行它,你可以看到文本被选中:
<p id="p">Example: <i>italic</i> and <b>bold</b></p>
<script>
let range = new Range();
range.setStart(p, 0);
range.setEnd(p, 2);
// 范围的 toString 以文本形式返回其内容,不带标签
console.log(range); // Example: italic
// 将此范围应用于文档选择,后文有解释
document.getSelection().addRange(range);
</script>
这是一个更灵活的测试台,你可以在其中设置范围开始/结束编号,并探索各种情况:
<p id="p">Example: <i>italic</i> and <b>bold</b></p>
From <input id="start" type="number" value=1> – To <input id="end" type="number" value=4>
<button id="button">Click to select</button>
<script>
button.onclick = () => {
let range = new Range();
range.setStart(p, start.value);
range.setEnd(p, end.value);
// 应用选择,后文有解释
document.getSelection().removeAllRanges();
document.getSelection().addRange(range);
};
</script>
例如,在同一个 <p> 中从偏移量 1 到 4 选择得到的范围为 <i>italic</i> and <b>bold</b>:
我们不是必须在 setStart 和 setEnd 中使用相同的节点。一个范围可能会跨越很多不相关的节点。唯一要注意的是终点要在起点之后。
选择更大的片段
让我们在示例中选择一个更大的片段,像这样:
我们已经知道如何实现它了。我们只需要将起点和终点设置为文本节点中的相对偏移量即可。
我们需要创建一个范围,它:
- 从
<p>的第一个子节点的位置 2 开始(选择 "Example: " 中除前两个字母外的所有字母) - 到
<b>的第一个子节点的位置 3 结束(选择 “bold” 的前三个字母,就这些):
<p id="p">Example: <i>italic</i> and <b>bold</b></p>
<script>
let range = new Range();
range.setStart(p.firstChild, 2);
range.setEnd(p.querySelector('b').firstChild, 3);
console.log(range); // ample: italic and bol
// 使用此范围进行选择(后文有解释)
window.getSelection().addRange(range);
</script>
正如你所看到的,选择我们想要的范围其实很容易实现。
如果我们想将节点作为一个整体,我们可以将元素传入 setStart/setEnd。否则,我们可以在文本层级上进行操作。
range 属性
我们在上面的示例中创建的 range 对象具有以下属性:
startContainer,startOffset—— 起始节点和偏移量,- 在上例中:分别是
<p>中的第一个文本节点和2。
- 在上例中:分别是
endContainer,endOffset—— 结束节点和偏移量,- 在上例中:分别是
<b>中的第一个文本节点和3。
- 在上例中:分别是
collapsed—— 布尔值,如果范围在同一点上开始和结束(所以范围内没有内容)则为true,- 在上例中:
false
- 在上例中:
commonAncestorContainer—— 在范围内的所有节点中最近的共同祖先节点,- 在上例中:
<p>
- 在上例中:
选择范围的方法
有许多便利的方法可以操纵范围。
我们已经见过了 setStart 和 setEnd,这还有其他类似的方法。
设置范围的起点:
setStart(node, offset)