JavaScript 的 Range 和 DocumentFragment

原文链接:https://www.hezebin.com/article/6687947a02a8ac30292a18f8

Range

Range对象表示文档中节点和文本节点的一部分。它允许你创建、操作和检测文档中的任意部分。文档:https://developer.mozilla.org/en-US/docs/Web/API/Range

创建 Range 对象

可以通过 document.createRange() 方法来创建一个新的 Range 对象。

let range = document.createRange();

Range 对象的属性

collapsed: 一个布尔值,表示 Range 是否为空(即开始和结束位置是否相同)。
commonAncestorContainer: 包含 Range 开始和结束点的最深公共祖先节点。
startContainer: Range 的起始节点。
startOffset: Range 的起始位置相对于起始节点的偏移量。
endContainer: Range 的结束节点。
endOffset: Range 的结束位置相对于结束节点的偏移量。

使用 Range 对象

设置 Range 的开始和结束

可以使用 setStartsetEnd 方法来设置 Range 的开始和结束位置。

let range = document.createRange();
let startNode = document.getElementById("startNode");
let endNode = document.getElementById("endNode");

range.setStart(startNode, 0);
range.setEnd(endNode, endNode.childNodes.length);

选取节点中的部分内容

let range = document.createRange();
let textNode = document.getElementById("textNode").firstChild;

range.setStart(textNode, 5);
range.setEnd(textNode, 10);

!!! abstract 注意!
如果setStartstartNodeTextCommentCDataSection 类型的节点,则 startOffset 表示从 startNode 开始的字符数量。
对于其他类型的节点,startOffset 表示从 startNode 开始的子节点数量。setEnd 同理。
!!!

Range 对象的操作方法

  1. selectNode: 选中一个节点及其所有子节点。
range.selectNode(document.getElementById("myElement"));
  1. selectNodeContents: 选中一个节点的内容。
range.selectNodeContents(document.getElementById("myElement"));
  1. collapse: 将 Range 折叠到起始或结束点。
range.collapse(true);  // 折叠到起始点
  1. cloneRange: 创建一个 Range 对象的副本。
let newRange = range.cloneRange();
  1. deleteContents: 从文档中删除 Range 表示的内容。
range.deleteContents();
  1. extractContents: 从文档中提取 Range 表示的内容。
let documentFragment = range.extractContents();

extractContents 方法会从文档中移除 Range 表示的所有内容,并将这些内容作为一个 DocumentFragment 返回。这个方法不仅会提取文本内容,还会提取 HTML 元素,且还会包含它们的父节点。

  1. insertNode: 在 Range 的起始位置插入一个节点。
let newNode = document.createElement("span");
range.insertNode(newNode);
  1. surroundContents: 用一个节点包裹 Range 表示的内容。
let wrapper = document.createElement("div");
range.surroundContents(wrapper);

控制 Range 中的内容

可以通过设置 startContainerstartOffsetendContainerendOffset 来精确控制 Range 的内容。例如:

假设我们有以下 HTML 结构:

<div id="startNode">Hello <b>world</b></div>
<div id="endNode"><i>This</i> is a <span>test</span></div>
<div id="appendNode"></div>

使用 extractContents()

const range = document.createRange()
const startNode = document.getElementById('startNode')?.firstChild
const endNode = document.getElementById('endNode')?.lastChild?.lastChild

if (!startNode || !endNode) {
  return
}
range.setStart(startNode, 2) // 偏移 2,以 "Hello " 的第 3 个字符 l 开头
range.setEnd(endNode, 2) // 偏移 2,范围到以 "<span>test</span>" 下 test 的第二个字符 e 结尾
const documentFragment = range.extractContents()
document.getElementById('appendNode')?.appendChild(documentFragment) // 将提取的内容附加到文档的末尾

结果:

<div id="startNode">He</div>
<div id="endNode"><span>st</span></div>
<div id="appendNode">
    <div id="startNode">llo <b>world</b></div>
    <div id="endNode"><i>This</i> is a <span>te</span></div>
</div>

这里可以看到使用extractContents获取到的所选范围的内容包含其父元素

DocumentFragment

DocumentFragment 是一个轻量级的文档对象,可以作为文档的一部分处理。它是一个空的文档片段,没有父级,当你在文档中插入它时,它会被替换为其所有的子节点,而不是它本身。这个特性使 DocumentFragment 成为批量操作 DOM 树的一种有效方法。

DocumentFragment 的特点

  • 轻量级:DocumentFragment 是一个没有父级的节点,其所有操作不会引起 DOM 重排和重绘,直到它被附加到文档中。
  • 内存高效:因为它仅存在于内存中,不附加到 DOM 树上,所以操作速度更快,并且性能更高。
  • 批量操作:你可以在 DocumentFragment 上进行多次 DOM 操作,然后一次性将它附加到实际的 DOM 树中,减少重排和重绘次数。

使用 DocumentFragment

下面是一些示例,展示如何创建和使用 DocumentFragment

示例1:创建和添加子节点

// 创建一个 DocumentFragment
const fragment = document.createDocumentFragment();

// 创建一些新的 DOM 元素
const newElement1 = document.createElement('div');
newElement1.textContent = 'This is a div';

const newElement2 = document.createElement('p');
newElement2.textContent = 'This is a paragraph';

// 将新元素添加到 DocumentFragment 中
fragment.appendChild(newElement1);
fragment.appendChild(newElement2);

// 将 DocumentFragment 一次性附加到文档中
document.body.appendChild(fragment);

在这个例子中,newElement1newElement2 被添加到 fragment 中,然后 fragment 被一次性添加到文档的 body 中。这种方法减少了直接操作 DOM 的次数,提高了性能。

示例2:与 Range 一起使用

// 假设你有以下 HTML 结构
// <div id="content">This is <b>some</b> content</div>

// 创建一个 Range
const range = document.createRange();
const contentDiv = document.getElementById('content');

// 设置 Range 覆盖范围
range.selectNodeContents(contentDiv);

// 提取范围内的内容到 DocumentFragment 中
const fragment = range.extractContents();

// 查看提取的内容
console.log(fragment);

// 可以将提取的内容重新附加到另一个节点中
document.body.appendChild(fragment);

在这个例子中,我们使用 Range 对象来选择 contentDiv 的内容,然后提取这些内容到一个 DocumentFragment 中。这个 DocumentFragment 包含了 contentDiv 的所有子节点,原始 contentDiv 的内容被清空。

Selection 中的 Range

JavaScript 中,RangeSelection 是两个密切相关的概念,用于处理和操作用户在页面上选择的文本或元素。Range 表示文档中的连续区域,而 Selection 代表用户的当前选择,它可以包含一个或多个 Range

document.addEventListener('mouseup', () => {
  const selection = window.getSelection();
  
  if (selection.rangeCount > 0) {
    const range = selection.getRangeAt(0); // 获取第一个 Range

    console.log('Selected text:', range.toString());

    // 示例操作:在选区开始处插入文本
    const newNode = document.createTextNode('【插入文本】');
    range.insertNode(newNode);
  }
});

手动划选时通常都只有一个 Range,都是连续的选区。多选区一般是自行创建的:

document.getElementById('createRanges').addEventListener('click', () => {
      const selection = window.getSelection();
      selection.removeAllRanges(); // 清除任何现有的 Range

      const range1 = document.createRange();
      const range2 = document.createRange();

      const para1 = document.getElementById('para1').firstChild;
      const para2 = document.getElementById('para2').firstChild;
      const para3 = document.getElementById('para3').firstChild;

      range1.setStart(para1, 0);
      range1.setEnd(para1, para1.length);

      range2.setStart(para3, 0);
      range2.setEnd(para3, para3.length);

      selection.addRange(range1);
      selection.addRange(range2);

      console.log('Selection contains', selection.rangeCount, 'ranges.');
    });
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值