Chrome翻译插件使用的Selection对象

293 阅读5分钟

背景:我们经常看官网的一些技术文档,当遇到不会的单词都需要查询一下,每次都需要打开一个翻译网站再粘贴对应的单词,翻译结束后再返回继续阅读文档,tab页之间的来回切换比较麻烦。后来发现了Chrome中的一些翻译插件,只要鼠标滑动既可以翻译对应单词,刚好符合需求。但Chrome翻译插件需要翻墙,这是痛点。 之后up主自己做了一款翻译插件,思路都是一样的。

如图所示

image.png

思路:在鼠标选中一段文字后弹出一个icon,点击即可翻译

问题:如何获取鼠标当前选中的文本,这就用到了今天的Selection对象

获取Selection对象

image.png

如图所示,用鼠标选中一段文字,然后控制台执行 window.getSelection()便可以获取到

var selection = window.getSelection();
console.log(selection);

Selection对象中的一些概念

先介绍三个概念,锚点,焦点,范围

锚点 (anchor)

蓝色选区的起点我们称为锚点 (anchor)。 锚点就是我们鼠标按下瞬间的那个点。 在用户拖动鼠标时,锚点是不会变的。

与锚点相关的有两个属性

  1. anchorNode: 该选区起点所在的节点
  2. anchorOffset 返回一个数字,其表示的是选区起点在 anchorNode 中的位置偏移量。 如果 anchorNode 是文本节点,那么返回的就是从该文字节点的第一个字开始,直到被选中的第一个字之间的字数(如果第一个字就被选中,那么偏移量为零)

焦点 (focus)

蓝色选取的终点我们称为焦点 (focus) 焦点是你的鼠标松开瞬间所记录的那个点。 随着用户拖动鼠标,焦点的位置会随着改变。

与焦点相关的也有两个属性

  1. focusNode 返回该选区终点所在的节点。
  2. focusOffset 返回一个数字,其表示的是选区终点在 focusNode 中的位置偏移量。 如果 focusNode 是文本节点,那么选区末尾未被选中的第一个字,在该文字节点中是第几个字(从 0 开始计),就返回它。

范围 (range)

整个蓝色区域就是范围 (range) 默认情况下,用户拖动鼠标选择文本,一个Selection对象只包含一个range。 但通过代码可以选中多个range

可以通过以下代码获取当前用户选中的区域

 Selection.getRangeAt(0)
 //返回当前第一个选区 一般用户只能选择一个选区

toString获取选中的文本

window.getSelection().toString()

可以获取选中区域的文本

翻译插件的小案例

思路

  1. 鼠标单击释放的时候,获取当前选中的文本,如果文本字符大于0,则在选中区域弹出一个icon,点击可以翻译
  2. 鼠标单击按下的时候,移除页面已有的icon小图标

对应的两个事件就是mousedown和mouseup

结果如图所示

image.png

源码 jsfiddle.net/whaleluo/91…

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Selection</title>
    <script>
      const containerDom = document.createElement("span");
      containerDom.style.position = "fixed";
      containerDom.style.transform = "translateX(-50%)";
      containerDom.style.backgroundColor = "transparent";
      containerDom.style.cursor = "pointer"
      const icon = document.createElement("img");
      icon.src = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAATCAYAAAByUDbMAAACwElEQVQ4jY2Uz2tUVxTHP+e+N88kMzbTJP4gxWTGJIoGaTK4EQXTIrUIYipUUbpoUdwWFwX3Ltx0UXDpwr+gktDSLrpoMQpaGiIqRjv+IC4ScbRJnZnojO/d08X7MRM7why43PvuvefzvufHe3L8YnXOWsYUQEEBVSCabVBbmf6u50PaMGMtY6qRYwRQwEYzxssePv9ksi1Y7NzKwn2B1Poj7cBcpQn2Pqoxk7fvFHN9vZnkkrU2dNHkedqNcxXblh6lMxVu3X8moTbjZWcf1ycOZC2e56KqiMgamIhMuDSFeWqfz95hm4CvFQ2XZlwU4f6LLnavvKKnpxvHMWtAESwKU6EwYNk7bJm65TA151AYsMwvmeRycXk9tVqZSqVCJpPGmMZZko24AFt6ldU6TM057Bu2fDZq+faAz+R4gAJ17eDuooPvB/x09QaLpX8SSKzQjYWu1qDLg760UqoI80vCF+MBpXKsQJj6619+v34VJyX8ee8hjpsChInCKBOFUdw4YbMLhsnxgHOHfGaKhh2bw4OZv52kmesdeQq5RYYG+kCEK3/c5Ogne8h/tCkKMypmqSxc+CXFwstQUaen/PCby/ySNIrtZJh58JrbxQWePivRne5ix2A/3emuUPvh76ut20wbU5wTBTbJI/TlrxgHvvp8P7n+jRhjcF23oSzpt2hoCxAKy7afIFAUWKlUkwL4vt/I2bvK1kCaFjXtYM/O/eT7yvx8bRYRYWxbHlXFtUENcdatAbQKWZOl8PztZg4NZnEcw817RT4eyYVNG9TLZ8W8OfIupRlmK9UxVZsNXwhPqj5vdyu7hgYTVQBCG/bp0R8vi+n4GoWUazl9YgPbhz7A81yMEUTC8f9vooVZ60+LCJ4HZ05uZCSfIZUygDb/NdpTBnDw2JXlb77ckN2aS7POcxpqjMEYQ71ev/UfWKE92u2OneAAAAAASUVORK5CYII=";
      icon.style.width = "19px";
      containerDom.appendChild(icon);

      const getSelectionRectDimensions = () => {
        let width = 0,
          height = 0,
          dx = 0,
          dy = 0,
          text = '';
        if (window.getSelection) {
          const selection = window.getSelection();
          if (selection.rangeCount) {
            const range = selection.getRangeAt(0).cloneRange();
            if (range.getBoundingClientRect) {
              const rect = range.getBoundingClientRect();
              width = rect.right - rect.left;
              height = rect.bottom - rect.top;
              dx = rect.left;
              dy = rect.top;
              text = selection.toString()
            }
          }
        }
        return { text, width, height, dx, dy };
      };
      const mouseupEvent = () => {
        const rect = getSelectionRectDimensions();
        console.log(rect)
        if (rect.text === '') {
          containerDom.style.display = "none";
          if(document.body.contains(containerDom)){
            document.body.removeChild(containerDom);
          }
          return;
        }
        containerDom.style.display = "block";
        containerDom.style.top = rect.dy + rect.height + 5 + "px";
        containerDom.style.left = rect.dx + rect.width / 2 + "px";
        document.body.appendChild(containerDom);
      };

      const mousedownEvent = ()=>{
        containerDom.style.display = "none";
        if(document.body.contains(containerDom)){
            document.body.removeChild(containerDom);
        }
      }

      document.addEventListener("mouseup", mouseupEvent);
      document.addEventListener("mousedown",mousedownEvent)
    </script>
  </head>
  <body>
    <div id="myDiv" style="color: red; margin-top: 100px">
      Selection 对象所对应的是用户所选择的
      ranges(区域),俗称拖蓝。默认情况下,该函数只针对一个区域
    </div>
  </body>
</html>

其他方法

Selection对象方法

  1. getRangeAt 返回选择的第n个区域
    • Selection.getRangeAt(0),返回当前第一个选取 一般用户只能选择一个选区
  2. addRange() 向选区(Selection)中添加一个区域(Range)。
  3. removeRange,removeAllRanges 将所有的区域都从选区中移除。
  4. 将选区折叠为一个点
    • collapseToStart,将当前的选区折叠到起始点。
    • collapseToEnd 将当前的选区折叠到最末尾的一个点。
    • collapse 将当前的选区折叠为一个点。
  5. containsNode 判断某一个 Node 是否为当前选区的一部分。
  6. deleteFromDocument从页面中删除选区中的内容。
  7. selectAllChildren 把指定元素的所有子元素设为选中区域,并取消之前的选中区域。
  8. extend(node, offset) 移动选中区的起点到指定的点。选中区的起点不会移动。选中区将从起点开始到新的终点
  9. modify 以通过简单的文本命令来改变当前选区或光标位置。

扩展

  1. 思路可以扩展为文本选中后调用讯飞接口,语音阅读,然后将选中的单词和结果存储到本地,形成本地词典
  2. 翻译接口和发音接口我们会调用第三方api,有的是收费的,形成本地词典后,相同的单词不再去服务端请求,直接本地获取(也可以用服务端缓存技术)
  3. 后期报表统计我们常用的单词查询频率,便于复习。

Chrome插件的开发流程就不赘述了,这个官网demo已经很多了