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 在现代浏览器中支持良好,但在旧版本的浏览器中可能存在兼容性问题。
相关推荐
zizisuo2 小时前
16000+字!Java集合笔记
java·开发语言
这儿有一堆花2 小时前
告别 Class 组件:拥抱 React Hooks 带来的函数式新范式
前端·javascript·react.js
BeingACoder2 小时前
【SAA】SpringAI Alibaba学习笔记(二):提示词Prompt
java·人工智能·spring boot·笔记·prompt·saa·springai
熊猫钓鱼>_>2 小时前
Java面向对象核心面试技术考点深度解析
java·开发语言·面试·面向对象··class·oop
十二春秋3 小时前
场景模拟:基础路由配置
前端
六月的可乐3 小时前
实战干货-Vue实现AI聊天助手全流程解析
前端·vue.js·ai编程
黄暄3 小时前
微服务面试题(14题)
java·spring cloud·微服务·架构·java-rabbitmq·java-zookeeper
DKPT3 小时前
如何设置JVM参数避开直接内存溢出的坑?
java·开发语言·jvm·笔记·学习
萤丰信息3 小时前
智慧园区系统:开启园区管理与运营的新时代
java·大数据·人工智能·安全·智慧城市·智慧园区