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 在现代浏览器中支持良好,但在旧版本的浏览器中可能存在兼容性问题。
相关推荐
恋爱绝缘体116 分钟前
2020重学C++重构你的C++知识体系
java·开发语言·c++·算法·junit
yinuo20 分钟前
前端跨页面通讯终极指南⑨:IndexedDB 用法全解析
前端
wszy18091 小时前
新文章标签:让用户一眼发现最新内容
java·python·harmonyos
xkxnq1 小时前
第二阶段:Vue 组件化开发(第 16天)
前端·javascript·vue.js
wszy18091 小时前
顶部标题栏的设计与实现:让用户知道自己在哪
java·python·react native·harmonyos
烛阴1 小时前
拒绝配置地狱!5 分钟搭建 Three.js + Parcel 完美开发环境
前端·webgl·three.js
Van_Moonlight1 小时前
RN for OpenHarmony 实战 TodoList 项目:空状态占位图
javascript·开源·harmonyos
xkxnq1 小时前
第一阶段:Vue 基础入门(第 15天)
前端·javascript·vue.js
程序员小假2 小时前
我们来说一下无锁队列 Disruptor 的原理
java·后端
资生算法程序员_畅想家_剑魔2 小时前
Kotlin常见技术分享-02-相对于Java 的核心优势-协程
java·开发语言·kotlin