JavaScript Selection API 是用于操作和获取用户在网页中选中的文本内容的接口。它提供了一系列方法和属性,使开发者能够获取、修改和监控用户在页面上的文本选择,可以用于实现富文本编辑器、文本高亮、自定义选择逻辑等功能。以下是 Selection API 的详细解析:
1. 核心概念
Selection API 主要涉及以下几个核心对象和概念:
Selection对象 :表示用户在页面中选中的文本范围。可以通过window.getSelection()获取当前的 Selection 对象。Range对象:表示文档中的一个连续范围,可以包含部分或全部节点。Selection 对象可以包含一个或多个 Range 对象。- 锚点(
Anchor)和焦点(Focus):Selection 对象的起始点称为锚点,结束点称为焦点。如果用户从左向右选择文本,锚点在左,焦点在右;反之亦然。
2. Selection 对象的属性
| 属性 | 描述 |
|---|---|
anchorNode |
返回 Selection 的锚点所在的节点。 |
anchorOffset |
返回 Selection 的锚点在 anchorNode 中的偏移量。 |
focusNode |
返回 Selection 的焦点所在的节点。 |
focusOffset |
返回 Selection 的焦点在 focusNode 中的偏移量。 |
isCollapsed |
如果 Selection 的起始点和结束点相同,返回 true,表示没有选中任何文本。 |
rangeCount |
返回 Selection 中包含的 Range 对象的数量。 |
type |
返回 Selection 的类型,如 "None"、"Caret" 或 "Range"。 |
3. Selection 对象的方法
| 方法 | 描述 |
|---|---|
getRangeAt(index) |
返回 Selection 中指定索引的 Range 对象。 |
addRange(range) |
向 Selection 中添加一个 Range 对象。 |
removeRange(range) |
从 Selection 中移除一个 Range 对象。 |
removeAllRanges() |
移除 Selection 中的所有 Range 对象。 |
collapse(node, offset) |
将 Selection 的起始点和结束点移动到指定的节点和偏移量。 |
extend(node, offset) |
将 Selection 的焦点移动到指定的节点和偏移量。 |
selectAllChildren(node) |
将 Selection 的范围扩展到包含指定节点的所有子节点。 |
deleteFromDocument() |
从文档中删除 Selection 的内容。 |
toString() |
返回 Selection 的文本内容。 |
4. Selection 对象使用示例
4.1. 示例:获取选中的文本
javascript
const selection = window.getSelection();
if (!selection.isCollapsed) {
const selectedText = selection.toString();
console.log("选中的文本:", selectedText);
}
4.2. 示例:修改选中的文本
javascript
const selection = window.getSelection();
if (!selection.isCollapsed) {
const range = selection.getRangeAt(0);
const newTextNode = document.createTextNode("新文本");
range.deleteContents(); // 删除选中的内容
range.insertNode(newTextNode); // 插入新文本
}
4.3. 监控 Selection 的变化
可以通过监听 selectionchange 事件来监控 Selection 的变化:
javascript
document.addEventListener("selectionchange", () => {
const selection = window.getSelection();
console.log("Selection 变化:", selection.toString());
});
5. Range 对象的属性
| 属性 | 描述 |
|---|---|
startContainer |
返回 Range 的起始节点。 |
startOffset |
返回 Range 的起始节点中的偏移量。 |
endContainer |
返回 Range 的结束节点。 |
endOffset |
返回 Range 的结束节点中的偏移量。 |
collapsed |
如果 Range 的起始点和结束点相同,返回 true。 |
commonAncestorContainer |
返回包含 startContainer 和 endContainer 的最深层的共同祖先节点。 |
6. Range 对象的方法
6.1 设置 Range 的范围
| 方法 | 描述 |
|---|---|
setStart(node, offset) |
设置 Range 的起始点为指定节点和偏移量。 |
setEnd(node, offset) |
设置 Range 的结束点为指定节点和偏移量。 |
setStartBefore(node) |
将 Range 的起始点设置为指定节点之前。 |
setStartAfter(node) |
将 Range 的起始点设置为指定节点之后。 |
setEndBefore(node) |
将 Range 的结束点设置为指定节点之前。 |
setEndAfter(node) |
将 Range 的结束点设置为指定节点之后。 |
示例:
javascript
const range = new Range();
const element = document.getElementById("example");
range.setStart(element, 0); // 将起始点设置为 element 的第一个子节点之前
range.setEnd(element, 1); // 将结束点设置为 element 的第一个子节点之后
6.2 选择节点
| 方法 | 描述 |
|---|---|
selectNode(node) |
将 Range 设置为包含指定节点及其所有子节点。 |
selectNodeContents(node) |
将 Range 设置为包含指定节点的所有子节点。 |
示例:
javascript
const range = new Range();
const element = document.getElementById("example");
range.selectNode(element); // 选择整个 element 节点
// 或
range.selectNodeContents(element); // 选择 element 的所有子节点
6.3 操作 Range 的内容
| 方法 | 描述 |
|---|---|
deleteContents() |
删除 Range 所包含的内容。 |
extractContents() |
从文档中提取 Range 的内容,并返回一个 DocumentFragment。 |
cloneContents() |
克隆 Range 的内容,并返回一个 DocumentFragment。 |
insertNode(node) |
在 Range 的起始点插入一个节点。 |
surroundContents(newParent) |
将 Range 的内容包裹在一个新的父节点中。 |
示例:
javascript
const range = new Range();
const element = document.getElementById("example");
range.selectNodeContents(element);
range.deleteContents(); // 删除 element 的所有子节点
// 或
const fragment = range.extractContents(); // 提取内容
// 或
const newNode = document.createElement("span");
range.insertNode(newNode); // 在 Range 的起始点插入新节点
6.4 比较 Range 的位置
| 方法 | 描述 |
|---|---|
compareBoundaryPoints(type, otherRange) |
比较两个 Range 的边界点。type 可以是:Range.START_TO_START、Range.END_TO_END、Range.START_TO_END、Range.END_TO_START。 |
isPointInRange(node, offset) |
检查指定的节点和偏移量是否在 Range 内。 |
intersectsNode(node) |
检查 Range 是否与指定节点相交。 |
示例:
javascript
const range1 = new Range();
const range2 = new Range();
const result = range1.compareBoundaryPoints(Range.START_TO_START, range2);
console.log(result); // -1, 0, 或 1
6.5 克隆和拓展 Range
| 方法 | 描述 |
|---|---|
cloneRange() |
克隆一个 Range 对象。 |
collapse(toStart) |
将 Range 的结束点移动到起始点。toStart 为 true 时,Range 将折叠到起始点;为 false 时,折叠到结束点。 |
detach() |
释放 Range 对象,使其不再引用文档。 |
示例:
javascript
const range = new Range();
range.setStart(element, 0);
range.setEnd(element, 1);
const clonedRange = range.cloneRange(); // 克隆 Range
range.collapse(true); // 折叠到起始点
7. 综合示例:使用JavaScript Selection API修改HTML页面所选择的文本的样式
以下示例展示两行文本和一个加粗按钮,任意选择部分文本,按下加粗按钮,选中的文本将被一个class属性为new-strong的strong结点包围,且原有的html标签结构不会被破坏。
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>加粗选中文本</title>
<style>
button {
margin: 10px;
padding: 5px 10px;
}
.new-strong {
color: green;
}
</style>
</head>
<body>
<div>第一块<span>234<span>567</span></span></div>
<div>第二块</div>
<button id="boldButton">加粗</button>
<script>
document.getElementById("boldButton").addEventListener("click", function() {
const selection = window.getSelection();
if (selection.rangeCount > 0) {
const range = selection.getRangeAt(0);
if (!range.collapsed) {
// 处理选中的文本
changeSelectedTextStyle(range, "strong", "new-strong");
// 清除选中状态
selection.removeAllRanges();
}
}
});
function changeSelectedTextStyle(range, tagName, className) {
// 获取选中的文本节点
const textNodes = getTextNodesInRange(range);
// 对每个文本节点进行处理
textNodes.forEach(node => {
const parent = node.parentNode;
const text = node.nodeValue;
const startOffset = node === range.startContainer ? range.startOffset : 0;
const endOffset = node === range.endContainer ? range.endOffset : text.length;
// 创建新节点并应用样式
const newNode = document.createElement(tagName);
newNode.classList.add(className);
if (startOffset > 0 || endOffset < text.length) { // 选中的内容为文本节点的一部分
// 截取被选中的部分
const beforeText = text.substring(0, startOffset);
const selectedText = text.substring(startOffset, endOffset);
const afterText = text.substring(endOffset);
// 创建新的文本节点
const beforeNode = document.createTextNode(beforeText);
const afterNode = document.createTextNode(afterText);
newNode.appendChild(document.createTextNode(selectedText));
// 在原文本节点前插入新的文本节点
parent.insertBefore(beforeNode, node);
parent.insertBefore(newNode, node);
parent.insertBefore(afterNode, node);
} else { // 整个文本节点被选中
// 将文本节点内容作为新节点的子节点
newNode.appendChild(node.cloneNode(true));
// 在原文本节点前插入新节点
parent.insertBefore(newNode, node);
}
// 删除原文本节点
parent.removeChild(node);
// 控制台输出当前节点修改后的内容以观察效果
if(parent !== document.body){
console.log(parent.outerHTML);
}
});
}
function getTextNodesInRange(range) {
const textNodes = [];
const startNode = range.startContainer;
const endNode = range.endContainer;
const startOffset = range.startOffset;
const endOffset = range.endOffset;
// 从 startNode 开始遍历,直到 endNode
let currentNode = startNode;
while (currentNode && currentNode !== endNode) {
if (currentNode.nodeType === Node.TEXT_NODE) {
textNodes.push(currentNode);
}
currentNode = getNextNode(currentNode);
}
// 添加 endNode(如果是文本节点)
if (endNode.nodeType === Node.TEXT_NODE) {
textNodes.push(endNode);
}
return textNodes;
}
function getNextNode(node) {
if (node.firstChild) {
return node.firstChild;
}
while (node) {
if (node.nextSibling) {
return node.nextSibling;
}
node = node.parentNode;
}
return null;
}
</script>
</body>
</html>
在浏览器中运行上面的网页,选择绿色部分文字(原本是无格式的),然后点击"加粗"按钮,无格式文本就变成了加粗绿色格式了:

原始的html结构:
<div>第一块<span>234<span>567</span></span></div>
<div>第二块</div>
在控制台中的输出如下:

可以看到没有破坏原始的html结构,文本格式化标签均为纯文本结点的直接父结点。
8. 注意事项
- 浏览器兼容性:Selection API 在现代浏览器中支持良好,但在旧版本的浏览器中可能存在兼容性问题。