使用Javascript如何获取选择文本所在的句子

这篇文章主要给大家爱介绍了关于利用javascript获取选择文本所在的句子的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起看看吧。

前言

最近收到一个 issue 期望能在划词的时候同时保存单词的上下文和来源网址。这个功能其实很久之前就想过,但感觉不好实现一直拖延没做。真做完发现其实并不复杂,完整代码在这里,或者继续往下阅读分析。话不多说了,来一起看看详细的介绍吧。

原理分析

获取选择文本

立即学习“Java免费学习笔记(深入)”;

通过 window.getSelection() 即可获得一个 Selection 对象,再利用 .toString() 即可获得选择的文本。

锚节点与焦节点

在 Selection 对象中还保存了两个重要信息,anchorNode 和 focusNode,分别代表选择产生那一刻的节点和选择结束时的节点,而 anchorOffset 和 focusOffset 则保存了选择在这两个节点里的偏移值。

这时你可能马上就想到第一个方案:这不就好办了么,有了首尾节点和偏移,就可以获取句子的头部和尾部,再把选择文本作为中间,整个句子不就出来了么。

当然不会这么简单哈stuck_out_tongue。

强调一下

一般情况下,anchorNode 和 focusNode 都是 Text 节点(而且因为这里处理的是文本,所以其它情况也会直接忽略),可以考虑这种情况:

Saladict is awesome!

登录后复制

如果选择的是“awesome”,那么 anchorNode 和 focusNode 都是 is awesome!,所以取不到前面的 “Saladict”。

另外还有嵌套的情况,也是同样的问题。

Saladict is awesome!

登录后复制

所以我们还需要遍历兄弟和父节点来获取完整的句子。

遍历到哪?

于是接下就是解决遍历边界的问题了。遍历到什么地方为止呢?我的判断标准是:跳过 inline-level 元素,遇到 block-level 元素为止。而判断一个元素是 inline-level 还是 block-level 最准确的方式应该是用 window.getComputedStyle() 。但我认为这么做太重了,也不需要严格的准确性,所以用了常见的 inline 标签来判断。

const INLINE_TAGS = new Set([ // Inline text semantics 'a', 'abbr', 'b', 'bdi', 'bdo', 'br', 'cite', 'code', 'data', 'dfn', 'em', 'i', 'kbd', 'mark', 'q', 'rp', 'rt', 'rtc', 'ruby', 's', 'samp', 'small', 'span', 'strong', 'sub', 'sup', 'time', 'u', 'var', 'wbr'])

登录后复制

原理总结

句子由三块组成,选择文本作为中间,然后遍历兄弟和父节点获取首尾补上。

实现

选择文本

先获取文本,如果没有则退出

const selection = window.getSelection()const selectedText = selection.toString()if (!selectedText.trim()) { return '' }

登录后复制

获取首部

对于 anchorNode 只考虑 Text 节点,通过 anchorOffset 获取选择在 anchorNode 的前半段内容。

然后开始补全在 anchorNode 之前的兄弟节点,最后补全在 anchorNode 父元素之前的兄弟元素。注意后面是元素,这样可以减少遍历的次数,而且考虑到一些被隐藏的内容不需要获取,用 innerText 而不是 textContent 属性。

let sentenceHead = ''const anchorNode = selection.anchorNodeif (anchorNode.nodeType === Node.TEXT_NODE) { let leadingText = anchorNode.textContent.slice(0, selection.anchorOffset) for (let node = anchorNode.previousSibling; node; node = node.previousSibling) { if (node.nodeType === Node.TEXT_NODE) { leadingText = node.textContent + leadingText } else if (node.nodeType === Node.ELEMENT_NODE) { leadingText = node.innerText + leadingText } } for ( let element = anchorNode.parentElement; element && INLINE_TAGS.has(element.tagName.toLowerCase()) && element !== document.body; element = element.parentElement ) { for (let el = element.previousElementSibling; el; el = el.previousElementSibling) { leadingText = el.innerText + leadingText } } sentenceHead = (leadingText.match(sentenceHeadTester) || [''])[0]}

登录后复制

最后从提取句子首部用的正则是这个

// match head   a.b is ok chars that ends a sentenceconst sentenceHeadTester = /((.(?![ .]))|[^.?!。?!…])+$/

登录后复制

前面的 ((.(?![ .])) 主要是为了跳过 a.b 这样的特别是在技术文章中常见的写法。

获取尾部

跟首部同理,换成往后遍历。最后的正则保留了标点符号

// match tail       for "..."const sentenceTailTester = /^((.(?![ .]))|[^.?!。?!…])+(.){0,2}/

登录后复制

压缩换行

拼凑完句子之后压缩多个换行为一个空白行,以及删除每行开头结尾的空白符

return (sentenceHead + selectedText + sentenceTail) .replace(/(^s+)|(s+$)/gm, '') // allow one empty line & trim each line .replace(/(^s+)|(s+$)/g, '') // remove heading or tailing 

登录后复制

完整代码

const INLINE_TAGS = new Set([ // Inline text semantics 'a', 'abbr', 'b', 'bdi', 'bdo', 'br', 'cite', 'code', 'data', 'dfn', 'em', 'i', 'kbd', 'mark', 'q', 'rp', 'rt', 'rtc', 'ruby', 's', 'samp', 'small', 'span', 'strong', 'sub', 'sup', 'time', 'u', 'var', 'wbr'])/*** @returns {string}*/export function getSelectionSentence () { const selection = window.getSelection() const selectedText = selection.toString() if (!selectedText.trim()) { return '' } var sentenceHead = '' var sentenceTail = '' const anchorNode = selection.anchorNode if (anchorNode.nodeType === Node.TEXT_NODE) { let leadingText = anchorNode.textContent.slice(0, selection.anchorOffset) for (let node = anchorNode.previousSibling; node; node = node.previousSibling) { if (node.nodeType === Node.TEXT_NODE) { leadingText = node.textContent + leadingText } else if (node.nodeType === Node.ELEMENT_NODE) { leadingText = node.innerText + leadingText } } for ( let element = anchorNode.parentElement; element && INLINE_TAGS.has(element.tagName.toLowerCase()) && element !== document.body; element = element.parentElement ) { for (let el = element.previousElementSibling; el; el = el.previousElementSibling) { leadingText = el.innerText + leadingText } } sentenceHead = (leadingText.match(sentenceHeadTester) || [''])[0] } const focusNode = selection.focusNode if (selection.focusNode.nodeType === Node.TEXT_NODE) { let tailingText = selection.focusNode.textContent.slice(selection.focusOffset) for (let node = focusNode.nextSibling; node; node = node.nextSibling) { if (node.nodeType === Node.TEXT_NODE) { tailingText += node.textContent } else if (node.nodeType === Node.ELEMENT_NODE) { tailingText += node.innerText } } for ( let element = focusNode.parentElement; element && INLINE_TAGS.has(element.tagName.toLowerCase()) && element !== document.body; element = element.parentElement ) { for (let el = element.nextElementSibling; el; el = el.nextElementSibling) { tailingText += el.innerText } } sentenceTail = (tailingText.match(sentenceTailTester) || [''])[0] } return (sentenceHead + selectedText + sentenceTail) .replace(/(^s+)|(s+$)/gm, '') // allow one empty line & trim each line .replace(/(^s+)|(s+$)/g, '') // remove heading or tailing }

登录后复制

上面是我整理给大家的,希望今后会对大家有帮助。

相关文章:

在ReactNative中如何使用Redux架构

在javascript中如何实现显式转换与隐式转换

在JavaScript中如何实现观察者模式

有关Angular2开发环境搭建(详细教程)

以上就是使用Javascript如何获取选择文本所在的句子的详细内容,更多请关注【创想鸟】其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至253000106@qq.com举报,一经查实,本站将立刻删除。

发布者:PHP中文网,转转请注明出处:https://www.chuangxiangniao.com/p/2749247.html

(0)
上一篇 2025年3月8日 04:58:23
下一篇 2025年3月8日 04:58:29

AD推荐 黄金广告位招租... 更多推荐

相关推荐

  • 在JS中笛卡尔积算法与多重数组笛卡尔积(详细教程)

    这篇文章主要介绍了js笛卡尔积算法与多重数组笛卡尔积实现方法,结合实例形式分析了javascript根据对象或数组生成笛卡尔积的相关操作技巧,需要的朋友可以参考下 本文实例讲述了JS笛卡尔积算法与多重数组笛卡尔积实现方法。分享给大家供大家参…

    编程技术 2025年3月8日
    200
  • 在javascript中如何实现按顺序加载运行js方法

    本篇文章主要教给大家如何在javascript中动态加载按顺序加载运行js的方法以及实现代码,需要的朋友参考学习下吧。 首先如果大家对JS动态加载有不理解的地方可以参阅: javascript动态加载实现方法 动态加载JS文件的三种方法 如…

    编程技术 2025年3月8日
    200
  • 在javascript中详细解读Function函数(详细教程)

    小编给大家带来一片关于javascript的基础教学内容,关于function函数的训练与理解,一起学习下吧。 Function函数是javascript的基础也是实现功能的一个引爆点,我们通过实例分析让你对Function函数有一个更加深…

    编程技术 2025年3月8日
    200
  • 使用JS如何实现单线程异步io回调

    这篇文章主要分析了javascript单线程异步io回调的特性这个问题,希望我们整理的内容能帮助到你。 我们最开始接触javascript应该大部分是从html中的js脚本开始,但是这种看似简单的语言稀里糊涂的用了好几年,也没有搞清楚它的一…

    编程技术 2025年3月8日
    200
  • 使用javascript如何修改浏览器title

    给大家讲一个用javascript修改浏览器title方法和技巧,需要的朋友把代码测试吧。 title在html中属于特殊的节点元素.因为它可以使用document.getElementsByTagName(“title&#82…

    编程技术 2025年3月8日
    200
  • 使用JQuery如何实现雪花飘落

    本文主要给大家讲述了如何用js和jquery两种方式实现雪花飘落的动画效果,有需要的朋友收藏一下吧。 很多朋友在做特效网页的时候需要用到雪花飘落的效果,我们这里给大家整理了分别用JS还有JQuery两种代码实现这个效果的方式。 我们先来看一…

    2025年3月8日
    200
  • 有关JavaScript异步(详细教程)

    写给小白看的javascript异步,具有一定的参考价值,感兴趣的小伙伴们可以参考一下 某天突然写了个方法要从后台调用数据,显示在前台页面,但是输出结果总是空undefined,得不到数据。多方找资料才发现,原来是入了JS异步的“坑”。 我…

    2025年3月8日
    200
  • 使用js如何实现隔行变色

    这篇文章主要为大家详细介绍了纯js实现隔行变色效果,具有一定的参考价值,感兴趣的小伙伴们可以参考一下 本文实例为大家分享了js实现隔行变色的具体代码,供大家参考,具体内容如下 function createTable(){ //创建表格 v…

    编程技术 2025年3月8日
    200
  • 在JavaScript中如何使用setter与getter方法

    这篇文章主要为大家详细介绍了javascript的setter与getter方法,具有一定的参考价值,感兴趣的小伙伴们可以参考一下 以前在写项目过程一直都没有使用过Javascript的setter与getter方法,所以对其是一种要懂不懂…

    编程技术 2025年3月8日
    200
  • 在javascript+css3中如何实现打气球小游戏

    这是一个简单但是印象深刻的小游戏,打气球小游戏的实现代码,主要基于js和css3,基于css3画气球,具体实现代码大家参考下本文 效果知识点: css3画气球, 自定义属性运用,随机阵列, DOM元素操作,高级回调函数与参数复传,动态布局,…

    2025年3月8日
    200

发表回复

登录后才能评论