最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

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

 更新時間:2017年12月03日 14:23:22   作者:CRIMX  
這篇文章主要給大家愛介紹了關(guān)于利用Javascript獲取選擇文本所在的句子的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起看看吧。

前言

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

原理分析

獲取選擇文本

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

錨節(jié)點與焦節(jié)點

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

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

當(dāng)然不會這么簡單哈stuck_out_tongue。

強(qiáng)調(diào)一下

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

<strong>Saladict</strong> is awesome!

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

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

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

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

遍歷到哪?

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

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'
])

原理總結(jié)

句子由三塊組成,選擇文本作為中間,然后遍歷兄弟和父節(jié)點獲取首尾補上。

實現(xiàn)

選擇文本

先獲取文本,如果沒有則退出

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

獲取首部

對于 anchorNode 只考慮 Text 節(jié)點,通過 anchorOffset 獲取選擇在 anchorNode 的前半段內(nèi)容。

然后開始補全在 anchorNode 之前的兄弟節(jié)點,最后補全在 anchorNode 父元素之前的兄弟元素。注意后面是元素,這樣可以減少遍歷的次數(shù),而且考慮到一些被隱藏的內(nèi)容不需要獲取,用 innerText 而不是 textContent 屬性。

let sentenceHead = ''
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]
}

最后從提取句子首部用的正則是這個

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

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

獲取尾部

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

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

壓縮換行

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

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
}

總結(jié)

以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,如果有疑問大家可以留言交流,謝謝大家對腳本之家的支持。

相關(guān)文章

  • JavaScript之AOP編程實例

    JavaScript之AOP編程實例

    這篇文章主要介紹了JavaScript的AOP編程,以實例形式分析了javascript面向切面編程的實現(xiàn)技巧,具有一定參考借鑒價值,需要的朋友可以參考下
    2015-07-07
  • JS實現(xiàn)簡單打磚塊彈球小游戲

    JS實現(xiàn)簡單打磚塊彈球小游戲

    這篇文章主要為大家詳細(xì)介紹了JS實現(xiàn)簡單打磚塊彈球小游戲,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2021-05-05
  • 如jQuery般易用的api風(fēng)格代碼分享

    如jQuery般易用的api風(fēng)格代碼分享

    最近幾天閑來無事,隨便寫了點代碼玩了玩。個人覺得,應(yīng)該把編碼當(dāng)做一種樂趣,要不然會覺得很累...
    2011-01-01
  • JS實現(xiàn)登錄頁密碼的顯示和隱藏功能

    JS實現(xiàn)登錄頁密碼的顯示和隱藏功能

    在登錄頁經(jīng)常會用到通過點擊文本框的類似小眼睛圖片來實現(xiàn)隱藏顯示密碼的功能。下面給大家介紹基于JS實現(xiàn)登錄頁密碼的顯示和隱藏功能,需要的朋友參考下吧
    2017-12-12
  • babel的使用及安裝配置教程

    babel的使用及安裝配置教程

    babel是一個廣泛使用的轉(zhuǎn)碼器,可以將ES6代碼轉(zhuǎn)化為ES5代碼,從而在現(xiàn)有環(huán)境執(zhí)行。這篇文章主要介紹了babel的使用以及安裝配置,需要的朋友可以參考下
    2018-02-02
  • JavaScript運行機(jī)制之事件循環(huán)(Event Loop)詳解

    JavaScript運行機(jī)制之事件循環(huán)(Event Loop)詳解

    這篇文章主要介紹了JavaScript運行機(jī)制之事件循環(huán)(Event Loop)詳解,本文從多個方面講解了Event Loop,需要的朋友可以參考下
    2014-10-10
  • JavaScript獲取表格(table)當(dāng)前行的值、刪除行、增加行

    JavaScript獲取表格(table)當(dāng)前行的值、刪除行、增加行

    這篇文章主要介紹了JavaScript獲取表格(table)當(dāng)前行的值、刪除行、增加行,本文直接給出代碼示例,需要的朋友可以參考下
    2015-07-07
  • js冒泡、捕獲事件及阻止冒泡方法詳細(xì)總結(jié)

    js冒泡、捕獲事件及阻止冒泡方法詳細(xì)總結(jié)

    javascript, jquery的事件中都存在事件冒泡和事件捕獲的問題,針對這兩個問題,本文給出詳細(xì)的解決方法,需要的朋友不要錯過
    2014-05-05
  • javascript OFFICE控件測試代碼

    javascript OFFICE控件測試代碼

    OFFICE控件測試代碼,主要是控制office的一些功能,需要的朋友可以參考下。
    2009-12-12
  • js中indexOf()的簡單使用示例

    js中indexOf()的簡單使用示例

    indexOf在js中有著重要的作用,可以判斷一個元素是否在數(shù)組中存在,或者判斷一個字符是否在字符串中存在,下面這篇文章主要給大家介紹了關(guān)于js中indexOf()簡單使用的相關(guān)資料,需要的朋友可以參考下
    2023-01-01

最新評論

南城县| 镇坪县| 湘潭县| 岗巴县| 南郑县| 淅川县| 嘉峪关市| 大港区| 喀什市| 民权县| 红原县| 芜湖市| 莫力| 柏乡县| 伊通| 新泰市| 扎囊县| 同德县| 阿勒泰市| 辽阳市| 盖州市| 伊春市| 南投县| 宁武县| 略阳县| 敦化市| 嘉兴市| 玉门市| 益阳市| 青河县| 巩义市| 栾城县| 富川| 通化市| 潜江市| 巍山| 中阳县| 郸城县| 嘉禾县| 巴南区| 绍兴市|