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

首頁 > 編程 > JavaScript > 正文

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

2019-11-19 14:47:57
字體:
來源:轉載
供稿:網友

前言

最近收到一個 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 標簽來判斷。

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}

總結

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

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
亚洲一区二区中文字幕| 一区二区三区 在线观看视| 黄色精品在线看| 亚洲精品久久久久久久久久久久久| 久久青草精品视频免费观看| 国产视频久久久| 久久男人av资源网站| 久久精品成人欧美大片古装| 亚洲第一视频在线观看| 中文字幕国内精品| 成人午夜高潮视频| 蜜臀久久99精品久久久无需会员| 日本久久91av| 亚洲天堂av在线免费| 国产精品aaa| 国产精品精品视频| 91精品国产高清久久久久久| 国产精品美女www爽爽爽视频| 亚洲视频国产视频| 亚洲精品视频网上网址在线观看| 亚洲精品国产免费| 亚洲天堂男人的天堂| 在线日韩第一页| 成人中文字幕+乱码+中文字幕| 欧美高清性猛交| 中文字幕av日韩| 国产精品大片wwwwww| 亚洲男人天天操| 久久91精品国产91久久久| 亚洲精品成人网| 亚洲国产精品电影在线观看| 亚洲精品99999| 亚洲码在线观看| 欧美成人免费大片| 成人精品网站在线观看| 色yeye香蕉凹凸一区二区av| 亚洲国产精品99| 成人在线播放av| 日韩电视剧在线观看免费网站| 久久亚洲精品网站| 欧美又大粗又爽又黄大片视频| 日韩免费视频在线观看| 久久久人成影片一区二区三区| 国产成人精品日本亚洲| 国产精品美女免费看| 亚洲人成自拍网站| 亚洲三级黄色在线观看| 亚洲xxx自由成熟| 亚洲a在线播放| 成人性生交大片免费看视频直播| 亚洲天堂网在线观看| 久久婷婷国产麻豆91天堂| 亚洲欧美国产va在线影院| 日韩精品中文字幕有码专区| 国产ts人妖一区二区三区| 91精品国产自产91精品| 国产精品一久久香蕉国产线看观看| 国产xxx69麻豆国语对白| 欧美精品电影在线| 国产精品视频色| 欧美一二三视频| 在线看片第一页欧美| 精品久久久久久国产91| 91中文字幕在线| 97不卡在线视频| 国产精品av在线播放| 亚洲天堂视频在线观看| 亚洲性生活视频| 亚洲性生活视频| 日韩成人激情影院| 精品亚洲一区二区| 45www国产精品网站| 国产中文字幕亚洲| 成人美女免费网站视频| 欧美日韩亚洲视频| 在线观看久久av| 久青草国产97香蕉在线视频| 日韩av电影手机在线| 综合网日日天干夜夜久久| 伊人久久免费视频| 中日韩美女免费视频网址在线观看| 亚洲福利在线视频| 国产视频精品免费播放| 国产精自产拍久久久久久蜜| 国产日韩综合一区二区性色av| 国产精品视频xxx| 欧美丰满少妇xxxxx做受| 欧美一区二区大胆人体摄影专业网站| 主播福利视频一区| 日韩精品有码在线观看| 日韩av片电影专区| 国内精品国产三级国产在线专| 国产精品偷伦一区二区| 欧美成人高清视频| 国产精品91在线观看| 久久久黄色av| 精品国产一区二区三区久久狼黑人| 国产精品久久久亚洲| 国产精品三级久久久久久电影| 国产精品91在线观看| 96sao精品视频在线观看| 亚洲free性xxxx护士白浆| 日韩精品视频在线| 日韩成人在线视频网站| 成人观看高清在线观看免费| 91精品视频在线播放| 日韩精品极品在线观看| 97视频免费在线观看| 成人激情视频在线观看| 中文字幕亚洲一区| 色偷偷88888欧美精品久久久| 夜夜狂射影院欧美极品| 日韩免费观看网站| 国模精品视频一区二区| 热99在线视频| 色777狠狠综合秋免鲁丝| 久久久久久综合网天天| 亚洲图片欧美日产| 日韩电影在线观看中文字幕| 国产精品久久久久久久久久久久久| 国产午夜精品麻豆| 亚洲一区二区三区在线免费观看| 成人激情视频在线播放| 川上优av一区二区线观看| 成人av在线亚洲| 国外日韩电影在线观看| 精品亚洲一区二区三区在线播放| 欧美日韩午夜剧场| 国产精品日韩欧美综合| 久久露脸国产精品| 欧亚精品中文字幕| 日韩av中文在线| 国产一区二区三区精品久久久| 日韩欧美国产成人| 久久躁狠狠躁夜夜爽| 国产婷婷色综合av蜜臀av| 中文字幕日韩综合av| 国产日韩欧美中文在线播放| 久久人人爽国产| 国产精品夫妻激情| 国产日韩在线精品av| 毛片精品免费在线观看| 亚洲欧美三级在线| 日韩电视剧在线观看免费网站| 热99久久精品| 欧美日韩国产在线看| 国产在线观看精品| 黄色一区二区三区| 在线激情影院一区| 揄拍成人国产精品视频| 欧美日韩国产精品一区二区三区四区| 国产成人在线一区二区| 91日本在线视频| 亚洲成在人线av| 国产欧美日韩丝袜精品一区| 亚洲国产精品久久久久久| 亚洲精品久久久久久久久| 精品久久久久久电影| 最近中文字幕日韩精品| 全球成人中文在线| 国产精品69精品一区二区三区| 日韩成人av在线播放| 亚洲专区国产精品| 中文字幕日本精品|