文本选区有啥魔力

348 阅读5分钟

最近在看mdn,很早之前就了解点文本选区,但是介于他API太多,当时就劝退自己了,这周闲下来梳理下。

getSelection() 返回一个 Selection 对象,表示用户选择(当前点击)的文本范围或光标的当前位置。它代表页面中的文本选区,可能横跨多个元素。文本选区由用户拖拽鼠标经过文字而产生。

我们来看看他提供的属性和方法吧。

getSelection()

属性

  • anchorNode返回该选区起点所在的节点。

  • anchorOffset返回一个数字,其表示的是选区起点在 anchorNode中的位置偏移量。

    • 如果 anchorNode 是文本节点,那么返回的就是从该文字节点的第一个字开始,直到被选中的第一个字之间的字数(如果第一个字就被选中,那么偏移量为零)。
    • 如果 anchorNode 是一个元素,那么返回的就是在选区第一个节点之前的同级节点总数。(这些节点都是 anchorNode 的子节点)
  • focusNode返回该选区终点所在的节点。

  • focusOffset返回一个数字,其表示的是选区终点在 focusNode中的位置偏移量。

  • 如果 focusNode 是文本节点,那么选区末尾未被选中的第一个字,在该文字节点中是第几个字(从 0 开始计),就返回它。选区起始和终止都在一个节点上时,他就是anchorOffset + 当前节点选中的文字个数

  • 如果 focusNode 是一个元素,那么返回的就是在选区末尾之后第一个节点之前的同级节点总数。

  • isCollapsed返回一个布尔值,用于判断选区的起始点和终点是否在同一个位置。表示当前是否有任何文本被选中

  • rangeCount 返回该选区所包含的连续范围的数量。chrome禁止使用连续选区。

  • type 描述当前选择的类型。

    • None: 当前没有选择。
    • Caret: 选区已折叠(即未处于选中状态)。
    • Range: 选择的是一个范围。

方法

getRangeAt(index)

获取指定索引的 Range 对象(index < rangeCount)。chrome永远是0。

addRange(range)

将 Range 对象添加到当前选择。由于chrome不能连续选区,所以这个api目前想到的唯一用处就是修改选区。

// 选中某个元素的全部文本
function selectElementText(element) {
  const range = document.createRange();
  range.selectNodeContents(element);
  const selection = window.getSelection();
  // 移除上次选区
  selection.removeAllRanges();
  selection.addRange(range);
}

image.png

removeAllRanges(), removeRange(range)

  • removeAllRanges() 清除所有选择范围。
  • removeRange(range) 移除某个指定选区。传入对应的选区对象。

collapse(node, offset)

可以收起当前选区到一个点。即取消当前选区,然后聚焦到对应的位置。

/* 将光标收起到文档 body 的开头 */
var body = document.getElementsByTagName("body")[0];
window.getSelection().collapse(body, 0);

collapseToEnd()  

取消当前选区,并把光标定位在原选区的最末尾处,如果此时光标所处的位置是可编辑的,且它获得了焦点,则光标会在原地闪烁。

collapseToStart()  

取消当前选区,并把光标定位在原选区的最开始处,如果此时光标所处的位置是可编辑的,且它获得了焦点,则光标会在原地闪烁。

containsNode(node, partlyContained)  

判断指定的节点是否被选中。参数二表示是否是部分包含。

 /* 检查 body 中是否有节点被选中 */
window.getSelection().containsNode(document.body, true)

deleteFromDocument()

直接在页面中删除选区中的内容。

window.getSelection().deleteFromDocument()

empty()

删除选中的区域。

window.getSelection().empty()

image.png

extend(node, offset)

扩展选择到新位置。

// node 焦点应该被拓展到的区域。
// offset 在node中的偏移。
window.getSelection().extend($0, 1)

image.png

modify(alter, direction, granularity)

改变当前选区或光标位置。

  • alter 改变类型。传入 "move" 来移动光标位置,或者 "extend" 来扩展当前选区。

  • direction调整选区的方向。你可以传入 "forward" 或 "backward" 来根据选区内容的语言书写方向来调整。或者使用 "left" 或 "right" 来指明一个明确的调整方向。

  • granularity调整的距离颗粒度。可选值有 "character""word""sentence""line""paragraph""lineboundary""sentenceboundary""paragraphboundary""documentboundary"

var selection = window.getSelection();
selection.modify("extend", "forward", "word"); // word 对于汉字会自己组词偏移

selectAllChildren(parentNode)

把指定元素的所有子元素设为选中区域,并取消之前的选中区域。

setBaseAndExtent(anchorNode, anchorOffset, focusNode, focusOffset)  

用来选中并设置在两个特定的 DOM 节点中文本选中的范围,并且选中的任何内容都位于两个节点之间。

  • anchorNode 选中内容的开始节点
  • anchorOffset选中范围内起点位置在锚节点下第几个子节点的位置。例如,如果是值为 0 的话,整个节点都是被选中的。如果值为 1 的话,那么整个节点至少有一个子节点被选中。以此类推。
  • focusNode选中内容的结尾节点
  • focusOffset选中范围内结束位置在焦点节点下第几个子节点的位置。例如,如果是值为 0 的话,整个节点都是被选中的。如果值为 1 的话,那么整个节点至少有一个子节点被选中。以此类推。

setPosition(node, offset)

将当前所选内容折叠为单个点。文档不会被修改。如果内容是聚焦的且可编辑的,则插入符号将在此处闪烁。

// node 光标所处的节点, 如果指定为null,那么将删除先前的所有选中区域
// offset 光标在节点中的偏移量

// 等价于removeRanges()
getSelection().setPosition(null) 

image.png

toString()

返回选择的纯文本内容。

选区对象 Range

上面我们一直在说选区(Range),那么Range是啥呢,下面我们来看看。

一个包含节点与文本节点的一部分的文档片段。下面这四种方式都可以获取Range对象

  • document.createRange
  • getSection().getRangeAt()
  • document.caretRangeFromPoint()
  • Range()

属性

  • collapsed表示 Range 的起始位置和终止位置是否相同。即是否有选中文本。
  • commonAncestorContainer 返回包含选区的起始和终止节点的共同父节点。

image.png

  • endContainer 返回包含 Range 终点的节点。getSelection().focusNode
getSelection().getRangeAt(0).endContainer === getSelection().focusNode // true
  • endOffset返回一个表示 Range 终点在 endContainer 中的位置的数字。即getSelection().focusOffset
getSelection().getRangeAt(0).endOffset === getSelection().focusOffset // true
  • startContainer返回包含 Range 开始的节点。getSelection().anchorNode
getSelection().getRangeAt(0).startContainer === getSelection().anchorNode // true
  • startOffset返回一个数字,表示 Range 在 startContainer 中的起始位置。即getSelection().anchorOffset
getSelection().getRangeAt(0).startOffset === getSelection().anchorOffset // true

方法

Range.cloneContents()  

返回一个 DocumentFragment,它是 Range中所有的 Node对象的副本。

image.png

Range.cloneRange()  

克隆一个Range对象。

克隆是按值复制的,而非按引用复制,因此其中一个Range的更改不会影响另一个。

getSelection().getRangeAt(0).cloneRange() === getSelection().getRangeAt(0) // false

Range.collapse()  

取消选区,并聚焦到选区的尾部。

image.png

Range.insertNode(newNode)

Range的起始位置插入节点。并成为选取的一部分。

新节点是插入在 Range 起始位置。如果将新节点添加到一个文本节点,则该节点在插入点处被拆分,插入发生在两个文本节点之间。如果新节点是一个文档片段,则插入文档片段的子节点。

image.png

Range.deleteContents()  

移除来自DocumentRange 内容。不像Range.extractContents一样,本方法不会返回一个包含删除内容的文本片段DocumentFragment等同于getSelection().deleteFromDocument() 如果选中区域包含元素也一同删除。

Range.extractContents()  

将 Range 的内容从文档树中移到 DocumentFragment 中。类似于getSelection().deleteFromDocument()

Range.getBoundingClientRect()  

一个 DOMRect 对象,该对象将选中区域中的内容包围起来;即该对象是一个将范围内所有元素的边界矩形包围起来的矩形。

Range.getClientRects()  

返回一个 DOMRect对象列表,表示range在屏幕上所占的区域。这个列表相当于汇集了范围中所有元素调用Element.getClientRects() 方法所得的结果。

Range.intersectsNode(Node)  

返回一个指示给定的 Node 是否与 Range相交的布尔值。即是否选中Node节点(部分)内容

selectNode(referenceNode)

将 Range设置为包含整个 Node 及其内容。Range的起始和结束节点的父节点与 referenceNode 的父节点相同。

Range.selectNodeContents(referenceNode)

设置选区选中referenceNode节点内容。startOffset 为 0,endOffset 则是引用节点包含的字符数或子节点个数。

Range.setEnd()

设置 Range的结束位置。 如果结束节点类型是 TextComment 或 CDATASection之一,那么 endOffset 指的是从结束节点算起字符的偏移量。对于其他 Node 类型节点,endOffset 是指从结束结点开始算起子节点的偏移量。

如果设置的结束点在起始点之上(在文档中的位置),将会导致选区折叠,起始点和结束点都会被设置为指定的结束位置。

Range.setEndBefore(referenceNode),Range.setEndAfter(referenceNode)

  • Range.setEndBefore(referenceNode) 修改选区范围,将Range的结束位置设置在另一个 Node 之前
  • Range.setEndAfter(referenceNode) 修改选区范围,将Range的结束位置设置在另一个 Node 之后

Range.setStart(startNode, startOffset)  

用于设置Range的开始位置。如果起始节点类型是 TextComment 或 CDATASection之一,那么 startOffset 指的是从起始节点算起字符的偏移量。对于其他 Node 类型节点,startOffset 是指从起始结点开始算起子节点的偏移量。

如果设置的起始位点在结束点之下(在文档中的位置),将会导致选区折叠,起始点和结束点都会被设置为指定的起始位置。

Range.setStartAfter()  

修改选区范围,将Range的起始位置设置在另一个 Node 之后(即选区内容不包括Node中的内容,如果设置的是选区共同的节点,那么将删除选区并将光标聚焦到referenceNode)。如果设置的节点在选区节点后方,那么将选区删除并将光标聚焦到referenceNode处。

Range.setStartBefore(referenceNode)

修改选区范围,将Range的起始位置设置在另一个 Node 之前(即选区内容包括Node中的内容)。如果设置的节点在选区节点后方,那么将选区删除并将光标聚焦到referenceNode处。

image.png

image.png

Range.surroundContents(node)

将node节点同样的标签包裹将Range的内容。

// 将内容用div元素包裹
getSelection().getRangeAt(0).surroundContents(document.createElement("div")) 

toString()

获取选区中的文本。等价于window.getSelection().toString()

如何在开发中使用起来呢

  • 高亮选中的文本
document.addEventListener("mouseup", () => {
  const selection = window.getSelection();
  if (selection.isCollapsed) return;

  const range = selection.getRangeAt(0);
  const highlight = document.createElement("span");
  highlight.className = "highlight";
  
  range.surroundContents(highlight);
  selection.removeAllRanges(); // 取消选中状态
});

// CSS
.highlight { background: yellow; }
  • 增强复制文本的功能
document.addEventListener("copy", (e) => {
  const selection = window.getSelection();
  const originalText = selection.toString();
  const appendedText = `${originalText}\n\n—— 来自掘金`;

  e.clipboardData.setData("text/plain", appendedText);
  e.preventDefault(); // 阻止默认复制行为
});
  • 禁止用户选择
// 监听选择事件,若选中禁止区域则清空选择
document.addEventListener("selectionchange", () => {
  const selection = window.getSelection();
  const forbiddenElement = document.querySelector(".no-select");
  
  if (forbiddenElement?.contains(selection.anchorNode)) {
    selection.removeAllRanges();
  }
});
  • 保留用户选中的html结构
function getSelectedHTML() {
  const selection = window.getSelection();
  if (selection.rangeCount === 0) return "";
  
  const range = selection.getRangeAt(0);
  const div = document.createElement("div");
  div.appendChild(range.cloneContents());
  return div.innerHTML;
}

往期年度总结

往期文章

专栏文章

🔥如果此文对你有帮助的话,欢迎💗关注、👍点赞、⭐收藏✍️评论,    支持一下博主~

公众号:全栈追逐者,不定期的更新内容,关注不错过哦!