JavaScript Selection API详解

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 返回包含 startContainerendContainer 的最深层的共同祖先节点。

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_STARTRange.END_TO_ENDRange.START_TO_ENDRange.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 的结束点移动到起始点。toStarttrue 时,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-strongstrong结点包围,且原有的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 在现代浏览器中支持良好,但在旧版本的浏览器中可能存在兼容性问题。
相关推荐
进阶小白猿10 小时前
Java技术八股学习Day30
java·开发语言·学习
2601_9498683610 小时前
Flutter for OpenHarmony 电子合同签署App实战 - 主入口实现
开发语言·javascript·flutter
m0_7482299910 小时前
Vue2 vs Vue3:核心差异全解析
前端·javascript·vue.js
C澒10 小时前
前端监控系统的最佳实践
前端·安全·运维开发
xiaoxue..10 小时前
React 手写实现的 KeepAlive 组件
前端·javascript·react.js·面试
摘星编程10 小时前
在OpenHarmony上用React Native:自定义useHighlight关键词高亮
javascript·react native·react.js
hhy_smile10 小时前
Class in Python
java·前端·python
小邓吖11 小时前
自己做了一个工具网站
前端·分布式·后端·中间件·架构·golang
南风知我意95711 小时前
【前端面试2】基础面试(杂项)
前端·面试·职场和发展
qq_124987075311 小时前
基于Srpingboot心晴疗愈社平台的设计与实现(源码+论文+部署+安装)
java·数据库·spring boot·spring·microsoft·毕业设计·计算机毕业设计