富文本编辑器开发系列——浏览器Selection API探究

1. Selection 基本属性

我们先上示例代码:

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6. <title>Selection API</title>
  7. </head>
  8. <body>
  9. <div id="box">
  10. <div onclick="showInfo()">
  11. <p>这里是一段普通的 <span>文字</span></p>
  12. <p>这里是另一段普通的 <span>文字</span></p>
  13. </div>
  14. </div>
  15. </body>
  16. <script>
  17. function showInfo() {
  18. var sel = window.getSelection();
  19. console.log(sel.toString());
  20. }
  21. </script>
  22. </html>

我们在本地运行起这个示例页面,并且在控制台中的source面板里,在console语句一行打上断点,从右向左选中第二行文字的一部分,然后看看当前的selection是什么样的:

富文本编辑器开发系列5——浏览器选区SelectionAPI探究 - 图1

在这里,可以看到当前Selection的所有属性:

  • anchorNode: 锚点,返回该选区起点所在的节点,这里的值是文字所在的文本节点,因为我们是从右向左拖动选择的,所以起点是该节点而不是这里是另一段普通的 所在的文本节点
  • anchorOffset: 锚点偏移量,表示的是选区起点在其所在节点中的位置偏移量,如果起点节点是文本节点,name返回的就是从该文字节点的第一个字开始,到被选中的第一个字之间的字数;如果起点节点是一个元素,则返回的就是在选区第一个节点之前的同级节点总数(这些节点都是起点节点的子节点)。这里我们的节点是文字节点,所以返回的是开始的字在文字这个文本节点中偏移量。
  • focusNode: 该选区终点所在的节点, 这里就是这是一段普通的这部分文字所在的文字节点
  • focusOffset: 与anchorOffset类型,这里是文本节点,并且选中内容在该节点开头,所以偏移量为0
  • isCollapsed: 是否闭合,此处开始节点和结束节点,开始偏移量与结束偏移量皆不相同,所以为false.
  • rangeCount: 包含的拖蓝区域数量,除了firfox外,其它浏览器默认都只有一个
  • type: 类型,这里的值是 Range ,代表当前是一个拖蓝区域

我们修改一下内容:

  1. <p>这里是另一段普通的 <span>文字内容啊</span></p>

然后还是从右向左拖动选择,但选择范围改变一下:

富文本编辑器开发系列5——浏览器选区SelectionAPI探究 - 图2

然后再看当前的偏移量:

富文本编辑器开发系列5——浏览器选区SelectionAPI探究 - 图3

印证了我们上面的说明。还要注意一点,对于开始节点来说,这里的偏移量是3,意味着选区的开始位置前面有3个字符,即选区是从第4个字符开始的,而对于结束节点,偏移量是4,说明选区结束位置是在该文本节点的第4个字之后。

2. Selection 选区API

getRangeAt(index) 获取拖蓝区域

我们在js部分增加两行代码:

  1. var range = sel.getRangeAt(0);
  2. console.log(range);

还是和上面一样的操作,然后还是在console语句处断点:

富文本编辑器开发系列5——浏览器选区SelectionAPI探究 - 图4

可以看到,成功获取到了当前的拖蓝对象,而我们在具体的网页编辑器功能开发中,通常用的最多的也是这个拖蓝对象,它的几个关键属性:

  • collapsed : 描述拖蓝是否闭合,如果我们只是在区域中某个文字后面点击鼠标,并不拖动选中文字,此时选区和拖蓝的开始节点与结束节点是同一个节点,偏移量也都相同,那么我们就说此时选区和拖蓝都是闭合的。
  • startContainer: 拖蓝的开始节点。拖蓝与选区不同,拖蓝的开始节点与选中时拖动方向无关。所以此例中的开始节点就是另一段普通的 所在的文本节点。
  • startOffset:开始节点偏移量,这里是3,同样意味着拖蓝的开始位置在开始节点(文本节点)第3个字符之后。
  • endContainer:拖蓝的结束节点。
  • endOffest:结束节点偏移量,这里是4,意味着拖蓝的结束位置在结束节点(文本节点)的第4个字符之后

collapse(parentNode, offset) 将当前选区闭合到指定节点的指定位置

我们继续在js部分增加代码:

  1. sel.collapse(document.getElementById('box'), 0);

然后还是按照上面的方法操作,这次不再断点。

我们发现,选中内容后点击选区,拖蓝消失了,其实就是选区闭合了,因为现在的页面内容还不是可编辑区域,所以闭合后没有看到有任何光标标识拖蓝闭合的位置。

我们改造下代码:

  1. <div id="box" contenteditable>
  2. <div onclick="showInfo()">
  3. <p id="first">这里是一段普通的 <span>文字</span>内容</p>
  4. <p id="second">这里是另一段普通的 <span>文字内容啊</span></p>
  5. </div>
  6. </div>
  7. //...
  8. <script>
  9. function showInfo() {
  10. var sel = window.getSelection();
  11. var range = sel.getRangeAt(0);
  12. sel.collapse(document.getElementById('first'), 2);
  13. }
  14. </script>

此时,按照上文的操作完成后,如下:

富文本编辑器开发系列5——浏览器选区SelectionAPI探究 - 图5

富文本编辑器开发系列5——浏览器选区SelectionAPI探究 - 图6

根据光标位置可知,选区闭合到了IDfirstp元素的第二个节点(<span>文字</span>)之后

富文本编辑器开发系列5——浏览器选区SelectionAPI探究 - 图7

extend(node, offset) 将选区焦点移动到特定位置。

此时,从选区中原来拖蓝的结束节点(即使是闭合拖蓝也一样)到指定的位置之间将形成新的拖蓝,原拖蓝将消失。

我们改造下js代码:

  1. var sel = window.getSelection();
  2. var range = sel.getRangeAt(0);
  3. sel.extend(document.getElementById('first'), 1);

仍旧按照上文中的操作,从右向左拖动选中另一段普通的 文字内容, 触发脚本后结果如下:

富文本编辑器开发系列5——浏览器选区SelectionAPI探究 - 图8

富文本编辑器开发系列5——浏览器选区SelectionAPI探究 - 图9

富文本编辑器开发系列5——浏览器选区SelectionAPI探究 - 图10

modify(alter, direction, tranularity) 修改选区

  • alter:操作类型,有两个可取值:

    • move: 移动光标位置
    • extend: 扩展选区范围
  • direction: 移动或扩展方向,有四个可取值

    • forward: 文本前进方向
    • backward : 文本后退方向
    • left : 向左
    • right : 向右
  • tranularity: 颗粒度,按照什么为单位进行移动或扩展

  • character: 每次移动或扩展一个字符的位置

    • word:每次移动或扩展一个单词的位置,英文状态下比较明显,中文状态下有时一个字就是一个单词(例如),有时一个词语是一个单词(例如普通),不确定。
    • sentence:每次移动或扩展一个句子的位置,以句号为分界(中英文句号皆可)。
    • line: 每次移动或扩展一行的位置,如果原开始位置在某行的第N个字符后,那移动或扩展后的位置就是上(下)一行的第N个字符后,如果上(下)一行字符总数小于N,则移动或扩展到行首(尾)。
    • paragraph: 每次移动或者扩展一个段落。偏移位置规则同上。
    • lineboundary: 移动或扩展到行首或行尾(根据方向)。
    • sentenceboundary: 移动或扩展到句首或句尾。
    • paragraphboundary: 移动或扩展到段首或段尾。
    • documentboundary : 移动或扩展到文档开头或结尾。

示例代码如下:

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6. <title>Selection API</title>
  7. </head>
  8. <body>
  9. <div id="box" contenteditable>
  10. <div onclick="showInfo()">
  11. <p id="first">这里是一段普通的 <span>文字</span>内容</p>
  12. <p id="second">这里是另一段普通的 <span id="third">文字内容啊</span></p>
  13. <p>这是一段带有英文的句子: this is a line with english</p>
  14. <p>这是一段带有英文的多行句子: this is a line with english。这是一段带有英文的多行句子: this is a line with english.这是一段带有英文的多行句子: this is a line with english,这是一段带有英文的多行句子: this is a line with english。这是一段带有英文的多行句子: this is a line with english。这是一段带有英文的多行句子: this is a line with english。这是一段带有英文的多行句子: this is a line with english,这是一段带有英文的多行句子: this is a line with english。</p>
  15. </div>
  16. </div>
  17. </body>
  18. <script>
  19. function showInfo() {
  20. var sel = window.getSelection();
  21. var range = sel.getRangeAt(0);
  22. sel.modify('move','backward', 'character');
  23. // sel.modify('extend','backward', 'character');
  24. // sel.modify('extend','forward', 'character');
  25. // sel.modify('extend','backward', 'word');
  26. // sel.modify('extend','backward', 'sentence');
  27. // sel.modify('extend','backward', 'line');
  28. // sel.modify('extend','backward', 'paragraph');
  29. // sel.modify('extend','backward', 'lineboundary');
  30. // sel.modify('extend','backward', 'sentenceboundary');
  31. // sel.modify('extend','backward', 'paragraphboundary');
  32. // sel.modify('extend','backward', 'documentboundary');
  33. }
  34. </script>
  35. </html>

可以直接将此代码复制到本地运行,并通过开放关闭js中不同的注释来查看不同参数下操作的差别。

富文本编辑器开发系列5——浏览器选区SelectionAPI探究 - 图11

富文本编辑器开发系列5——浏览器选区SelectionAPI探究 - 图12

collapseToStart() 取消当前选区,并把光标定位在原选区的最开始处

富文本编辑器开发系列5——浏览器选区SelectionAPI探究 - 图13

富文本编辑器开发系列5——浏览器选区SelectionAPI探究 - 图14

collapseToEnd() 取消当前选区,并把光标定位在原选区的结束位置

富文本编辑器开发系列5——浏览器选区SelectionAPI探究 - 图15

富文本编辑器开发系列5——浏览器选区SelectionAPI探究 - 图16

selectAllChildren(parentNode) 把指定元素所有子元素设为选中区域(不包含该元素本身),并取消之前选中区域

富文本编辑器开发系列5——浏览器选区SelectionAPI探究 - 图17

富文本编辑器开发系列5——浏览器选区SelectionAPI探究 - 图18

addRange(range) 将一个拖蓝区域加入选区当中

富文本编辑器开发系列5——浏览器选区SelectionAPI探究 - 图19

富文本编辑器开发系列5——浏览器选区SelectionAPI探究 - 图20

removeRange(range) 从选区中移除一个拖蓝区域

富文本编辑器开发系列5——浏览器选区SelectionAPI探究 - 图21

由于大多数浏览器一个选区都只能拖动出一个拖蓝区域,所以执行此操作后,用户选中的拖蓝区域将消失,且光标将消失。(注:并不会删除拖蓝区域中的内容)

富文本编辑器开发系列5——浏览器选区SelectionAPI探究 - 图22

removeAllRanges() 从当前选区中移除所有的拖蓝区区域

参看addRange在线代码示例中的用法

deleteFromDocument() 从页面中删除选区中的内容

富文本编辑器开发系列5——浏览器选区SelectionAPI探究 - 图23

富文本编辑器开发系列5——浏览器选区SelectionAPI探究 - 图24

toString() 返回代表当前selection对象的字符串,例如当前选择的文本文字

富文本编辑器开发系列5——浏览器选区SelectionAPI探究 - 图25

富文本编辑器开发系列5——浏览器选区SelectionAPI探究 - 图26

containsNode(aNode,aPartlyContained) 判断指定节点是否包含在当前选区内

富文本编辑器开发系列5——浏览器选区SelectionAPI探究 - 图27

  • aNode: 用于判断是否包含在选区中的那个节点
  • aPartlyContained: 当此参数为true时,即使选区只包含指定节点的一部分内容,该方法也将返回true,当此参数为false时,只有选区完全包含指定节点时,才返回true

富文本编辑器开发系列5——浏览器选区SelectionAPI探究 - 图28