亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 開發 > JS > 正文

利用Javascript獲取選擇文本所在的句子詳解

2024-05-06 16:41:14
字體:
來源:轉載
供稿:網友

前言

最近收到一個 issue 期望能在劃詞的時候同時保存單詞的上下文和來源網址。這個功能其實很久之前就想過,但感覺不好實現一直拖延沒做。真做完發現其實并不復雜,完整代碼在這里,或者繼續往下閱讀分析。話不多說了,來一起看看詳細的介紹吧。

原理分析

獲取選擇文本

通過 window.getSelection() 即可獲得一個 Selection 對象,再利用 .toString() 即可獲得選擇的文本。

錨節點與焦節點

在 Selection 對象中還保存了兩個重要信息,anchorNode 和 focusNode,分別代表選擇產生那一刻的節點和選擇結束時的節點,而 anchorOffset 和 focusOffset 則保存了選擇在這兩個節點里的偏移值。

這時你可能馬上就想到第一個方案:這不就好辦了么,有了首尾節點和偏移,就可以獲取句子的頭部和尾部,再把選擇文本作為中間,整個句子不就出來了么。

當然不會這么簡單哈stuck_out_tongue。

強調一下

一般情況下,anchorNode 和 focusNode 都是 Text 節點(而且因為這里處理的是文本,所以其它情況也會直接忽略),可以考慮這種情況:

<strong>Saladict</strong> is awesome!

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

另外還有嵌套的情況,也是同樣的問題。

Saladict is <strong><a href="#" rel="external nofollow" >awesome</a></strong>!

所以我們還需要遍歷兄弟和父節點來獲取完整的句子。

遍歷到哪?

于是接下就是解決遍歷邊界的問題了。遍歷到什么地方為止呢?我的判斷標準是:跳過 inline-level 元素,遇到 block-level 元素為止。而判斷一個元素是 inline-level 還是 block-level 最準確的方式應該是用 window.getComputedStyle() 。但我認為這么做太重了,也不需要嚴格的準確性,所以用了常見的 inline 標簽來判斷。

js;">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 = /((/.(?![ .]))|[^.?!。?!…/r/n])+$/

前面的 ((/.(?![ .])) 主要是為了跳過 a.b 這樣的特別是在技術文章中常見的寫法。

獲取尾部

跟首部同理,換成往后遍歷。最后的正則保留了標點符號

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

壓縮換行

拼湊完句子之后壓縮多個換行為一個空白行,以及刪除每行開頭結尾的空白符

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

完整代碼

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, '/n') // allow one empty line & trim each line .replace(/(^/s+)|(/s+$)/g, '') // remove heading or tailing /n}

總結

以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,如果有疑問大家可以留言交流,謝謝大家對VeVb武林網的支持。


注:相關教程知識閱讀請移步到JavaScript/Ajax教程頻道。
發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
日韩精品视频中文在线观看| 国产亚洲精品美女久久久| 欧美孕妇与黑人孕交| 国产精品视频网址| 亚洲电影免费观看高清| 欧美极品少妇xxxxx| 亚洲图片欧美午夜| 九九九热精品免费视频观看网站| 成人免费观看网址| 久久久成人的性感天堂| 成人免费黄色网| 日韩视频欧美视频| 亚洲欧美一区二区三区情侣bbw| 国产成人精品久久二区二区| 高清欧美一区二区三区| 久久精品国亚洲| 超碰91人人草人人干| 欧美极品少妇xxxxⅹ裸体艺术| 日韩中文视频免费在线观看| 中文字幕亚洲综合久久| 在线观看中文字幕亚洲| 国产欧美一区二区三区在线看| 亚洲综合视频1区| 久久影院免费观看| 日韩久久免费视频| 91色中文字幕| 国产91ⅴ在线精品免费观看| 一色桃子一区二区| 国产日本欧美一区| 国产日韩在线一区| 国产亚洲欧洲高清一区| 欧美另类69精品久久久久9999| 中文字幕久热精品视频在线| 成人av在线亚洲| 国产精品一二区| 欧洲精品毛片网站| 国产69久久精品成人看| 亚洲国产精品字幕| 一夜七次郎国产精品亚洲| 2025国产精品视频| 成人自拍性视频| 亚洲精品成人av| 欧美激情精品久久久久久久变态| 国产精品福利无圣光在线一区| 97福利一区二区| 欧美大全免费观看电视剧大泉洋| 高清欧美性猛交xxxx黑人猛交| 琪琪亚洲精品午夜在线| 欧美重口另类videos人妖| 欧美亚洲第一页| 亚洲男人的天堂在线播放| 亚洲欧洲激情在线| 国产精品国产自产拍高清av水多| 亚洲精品第一国产综合精品| 日韩一区二区三区xxxx| 91久久精品美女高潮| 国产91精品黑色丝袜高跟鞋| 91精品综合久久久久久五月天| 日韩电影免费观看在线| 国产成+人+综合+亚洲欧洲| 欧美裸身视频免费观看| 欧美国产在线视频| 欧美性xxxx极品hd满灌| 永久免费精品影视网站| 国产成人免费av| 亚洲性av在线| 精品久久久久久久久久| 欧美久久精品午夜青青大伊人| 国产日韩av在线| 欧洲一区二区视频| 亚洲欧美激情一区| 欧美在线视频一区二区| 欧美日韩国产综合视频在线观看中文| 精品久久久久久久大神国产| 久热国产精品视频| 欧美午夜精品伦理| 久久久久久久久久久av| 国内精品模特av私拍在线观看| 日韩美女在线观看一区| 亚洲图片在区色| 久久人人爽人人爽人人片亚洲| 欧美成人合集magnet| 中文字幕av日韩| 日韩中文字幕av| 久久久免费av| 久久视频在线看| 日韩电影视频免费| 亚洲欧美中文日韩v在线观看| 国产精品第一页在线| 亚洲无线码在线一区观看| 中文字幕在线亚洲| 国产精品视频1区| 91国自产精品中文字幕亚洲| 成人黄色av播放免费| 欧美成年人在线观看| 亚洲天堂网站在线观看视频| 韩国美女主播一区| 欧美成人精品在线播放| 日韩av影视综合网| 国产精品视频区| 日韩精品极品在线观看播放免费视频| 91av在线播放视频| 国产精品偷伦视频免费观看国产| 欧美日韩视频在线| 亚洲国产欧美一区二区丝袜黑人| 欧美午夜女人视频在线| 日韩一二三在线视频播| 国产中文字幕91| 国产一区二区欧美日韩| 国产精品一久久香蕉国产线看观看| 亚洲精品成人网| www国产91| 91在线中文字幕| 亚洲最大成人网色| 91wwwcom在线观看| 精品中文字幕在线观看| 91久久中文字幕| 亚洲欧美激情精品一区二区| 亚洲高清久久久久久| 中文字幕欧美日韩在线| 欧美日韩国产黄| 亚洲区bt下载| 69av在线视频| 国产激情久久久久| 亚洲精品网站在线播放gif| 国产精品国产三级国产专播精品人| 亚洲精品视频久久| 青青久久av北条麻妃海外网| 日本亚洲精品在线观看| 国产视频精品va久久久久久| 国产精品精品视频一区二区三区| 夜夜嗨av一区二区三区四区| 日韩在线视频播放| 久久久久久一区二区三区| 亚洲第一在线视频| 91国产精品视频在线| 国产一区二区视频在线观看| 国产精品爽黄69天堂a| 国产精品视频公开费视频| 俺去了亚洲欧美日韩| 欧美最顶级丰满的aⅴ艳星| 久久久久久久久亚洲| 日韩在线视频播放| 亚洲精品久久久久国产| 2021久久精品国产99国产精品| 日韩美女视频中文字幕| 欧美精品在线免费| 欧美黄色性视频| 欧美日韩国产一区在线| 亚洲第一精品久久忘忧草社区| 日韩成人高清在线| 亚洲精品国产欧美| 亚洲黄一区二区| 96sao精品视频在线观看| 日本欧美一级片| 亚洲成人网在线观看| 亚洲自拍偷拍网址| 欧美大片网站在线观看| 日韩精品999| 国产精品av网站| 在线精品国产成人综合| 欧美成在线视频| 国产成人一区二区三区小说| 欧美午夜激情在线|