js实现选择区域添加背景色

用到的基础web API

window.getSelection()

Selection.containsNode()

使用window.getSelection()并不能返回所有选中节点, 通过遍历所有选中节点的最小公共节点下的所有子节点, 并判断遍历到的节点是不是选中的节点, 可以找到所有选中的节点。

Selection.getRangeAt()

Range.commonAncestorContainer

直接遍历document下的所有子节点也可以, 但使用commonAncestorContainer获取所有选中节点的最近公共节点可以缩小遍历范围。

Text.splitText()

选择的开头和结尾部分,可能是半个单词, 即半个文本节点, 需要使用splitText将文本节点切割

源码实现

js 复制代码
import { v4 as uuidV4 } from 'uuid';

// 获取指定节点下的所有子节点
export function getAllNodes(rootNode, callback) {
  if (!callback) {
    var nodes = [];
    getAllNodes(rootNode, function (node) {
      nodes.push(node);
    });
    return nodes;
  }

  if (false === callback(rootNode)) return false;

  if (rootNode.hasChildNodes()) {
    let nodes = rootNode.childNodes;
    for (var i = 0, l = nodes.length; i < l; ++i)
      if (false === getAllNodes(nodes[i], callback)) return;
  }
}
// 监听mouseup事件
export function listenMouseUp() {
  document.addEventListener('mouseup', listenMouseUpCallback);
}

// 移除mouseup事件
export function removeListenMouseUp() {
  document.removeEventListener('mouseup', listenMouseUpCallback);
}

// mouseup事件的回调函数
function listenMouseUpCallback() {
  const selected = window.getSelection();
  const range = selected.getRangeAt(0);
  // range.insertNode(newNode()); // 在Range开头插入一个节点, 这是另外一种分隔方式
  const startContainer = range.startContainer;
  const startOffset = range.startOffset;
  startContainer.splitText(startOffset); // 将选中范围的结束位置处的文本节点分隔为两个节点

  const endContainer = range.endContainer;
  const endOffset = range.endOffset;
  endContainer.splitText(endOffset);

  const nodes = getAllNodes(range.commonAncestorContainer);
  let uuid = uuidV4();
  for (let i = 0; i < nodes.length; i++) {
    const node = nodes[i];
    if (selected.containsNode(node)) {
      addClass(node, uuid);
    }
  }
}

// 给选择区域增加背景色
function addClass(node, uuid) {
  if (node.nodeType === 3) {
    // 将文本节点外面加一层<span class="light-hight"></span>
    let newNode = document.createElement('span'); // 创建一个新的element节点
    newNode.classList.add('light-hight');
    newNode.classList.add('new-span');
    newNode.setAttribute('data-mark-id', uuid);
    node.parentNode.insertBefore(newNode, node); // 将新的节点插入到文本节点前
    newNode.appendChild(node); // 将文本节点移动到新的节点里面
  } else {
    node.classList.add('light-hight');
    node.setAttribute('data-mark-id', uuid);
  }
}
相关推荐
晓131335 分钟前
React篇——第三章 状态管理之 Redux 篇
前端·javascript·react.js
子兮曰40 分钟前
🚀24k Star 的 Pretext 为何突然爆火:它不是排版库,而是在重写 Web 文本测量
前端·javascript·github
@大迁世界44 分钟前
11.在 React.js 中,state 与 props 的差异体现在哪里?
前端·javascript·react.js·前端框架·ecmascript
Giant1001 小时前
🔥前端跨域封神解法:Vite Proxy + Express CORS,一篇搞定所有跨域坑!
前端·javascript·面试
像我这样帅的人丶你还1 小时前
JavaScript 迭代器详解
前端·javascript
Lazy_zheng1 小时前
Map / Set / WeakMap / WeakSet,一次给你讲透
前端·javascript·面试
意法半导体STM322 小时前
【官方原创】STM32H7双核芯片通过 STlink连接失败问题分析 LAT1654
开发语言·前端·javascript·stm32·单片机·嵌入式硬件
雨雨雨雨雨别下啦2 小时前
Vue3——RabbitShopping
前端·javascript·vue.js
学习指针路上的小学渣3 小时前
JavaScript笔记
前端·javascript
coder_Eight3 小时前
彻底吃透 Promise:从状态、链式到手写实现,再到 async/await 底层原理
javascript·面试