摘要:它的原理是深度優(yōu)先遞歸遍歷這個(gè)元素以及其子元素,通過不斷試探選中區(qū)域,并與鼠標(biāo)座標(biāo)對(duì)比來定位確切位置。原理現(xiàn)在總結(jié)一下原理通過獲得鼠標(biāo)所指最接近的元素以及文本位置。驗(yàn)證鼠標(biāo)此時(shí)在單詞區(qū)域范圍中。
最近終于抽空給 Saladict 實(shí)現(xiàn)了鼠標(biāo)懸浮取詞功能,使用了較為簡潔的實(shí)現(xiàn)方式,這里分享一下原理以及坑的處理。
初嘗試這個(gè)需求其實(shí)很早就被人提 issue 了,當(dāng)時(shí)做了一番搜索,最后嘗試了 document.caretPositionFromPoint / document.caretRangeFromPoint ,效果不太理想。
如果看 mdn 給的例子,就會(huì)發(fā)現(xiàn),它是遍歷每個(gè)元素添加事件的。這么做的原因是當(dāng)使用這個(gè)方法的時(shí)候,如果鼠標(biāo)指向元素空白的地方,它會(huì)就近取位置。所以例子通過給粒度更細(xì)的元素綁定來避免這個(gè)問題。然而實(shí)際上這么做還是不足夠的,一個(gè)段落末行也許只有幾個(gè)字符,這時(shí)空出接近一行,也會(huì)有上面的問題。
所以當(dāng)時(shí)就擱置了這個(gè)功能。
靈感直到最近,看到一個(gè)同類的開源劃詞翻譯擴(kuò)展 FairyDict 實(shí)現(xiàn)了取詞功能,遍觀摩了一番源碼。
它的原理是深度優(yōu)先遞歸遍歷這個(gè)元素以及其子元素,通過不斷試探選中區(qū)域,并與鼠標(biāo)座標(biāo)對(duì)比來定位確切位置。
有沒有發(fā)現(xiàn)問題,這個(gè)遍歷過程不正是上面 document.caretPositionFromPoint 干的事么,那么我們只需要最后量一下鼠標(biāo)是否在取詞范圍中即可。
原理現(xiàn)在總結(jié)一下原理:
通過 document.caretPositionFromPoint 獲得鼠標(biāo)所指最接近的元素以及文本位置 offset。
找出 offset 最接近的單詞。
通過 Range 獲得部分文本(單詞)的尺寸和座標(biāo)。
驗(yàn)證鼠標(biāo)此時(shí)在單詞區(qū)域范圍中。
選中這個(gè)單詞。Selection 支持直接添加 Range 。
實(shí)現(xiàn)按原理來實(shí)現(xiàn)就很簡單了。本文上按 alt 可體驗(yàn)取詞效果。
/** * @param {MouseEvent} e * @returns {void} */ function selectCursorWord (e) { const x = e.clientX const y = e.clientY let offsetNode let offset const sel = window.getSelection() sel.removeAllRanges() if (document["caretPositionFromPoint"]) { const pos = document["caretPositionFromPoint"](x, y) if (!pos) { return } offsetNode = pos.offsetNode offset = pos.offset } else if (document["caretRangeFromPoint"]) { const pos = document["caretRangeFromPoint"](x, y) if (!pos) { return } offsetNode = pos.startContainer offset = pos.startOffset } else { return } if (offsetNode.nodeType === Node.TEXT_NODE) { const textNode = offsetNode const content = textNode.data const head = (content.slice(0, offset).match(/[-_a-z]+$/i) || [""])[0] const tail = (content.slice(offset).match(/^([-_a-z]+|[u4e00-u9fa5])/i) || [""])[0] if (head.length <= 0 && tail.length <= 0) { return } const range = document.createRange() range.setStart(textNode, offset - head.length) range.setEnd(textNode, offset + tail.length) const rangeRect = range.getBoundingClientRect() if (rangeRect.left <= x && rangeRect.right >= x && rangeRect.top <= y && rangeRect.bottom >= y ) { sel.addRange(range) } range.detach() } }交互
最后,如果要提供功能開關(guān)或者設(shè)置不同按鍵的話,簡單的處理可以參考 FairyDict 讓事件處理空轉(zhuǎn)。但對(duì)于 mousemove 這類比較頻繁的事件,在關(guān)閉的時(shí)候取消事件監(jiān)聽可能更好一些。在 Saladict 中甚至將“面板被釘住”跟“普通情況”分開為不同的模式,這里借助 RxJS 來處理復(fù)雜的邏輯,可參考源碼。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/95615.html
摘要:在命令面板中你可以輸入命令進(jìn)行搜索中英文都可以,然后執(zhí)行。命名面板中可以執(zhí)行各種命令,包括編輯器自帶的功能和插件提供的功能。 本文主要介紹vscode在工作中常用的快捷鍵及插件,目標(biāo)在于提高工作效率本文的快捷鍵是基于mac的,windows下的快捷鍵放在括號(hào)里 Cmd+Shift+P(win Ctrl+Shift+P) [TOC] 零、快速入門 有經(jīng)驗(yàn)的可以跳過快速入門或者大致瀏覽一...
摘要:主要采用了原生與調(diào)用結(jié)合的功能實(shí)現(xiàn)功能。所以根據(jù)這種方法,讀者可以根據(jù)自己的需求添加更多的功能,比如在編輯框里面插入一個(gè)可以點(diǎn)擊的標(biāo)簽或者添加一個(gè)代碼塊希望能讀到此文章的讀者,能在下方一起交流,更希望大佬提出錯(cuò)誤,謝謝地址 因?yàn)橐粋€(gè)同學(xué),要做一個(gè)能加入圖片的留言板功能,類型與QQ空間留言板和百度貼吧發(fā)帖的那種形式,同時(shí)在網(wǎng)上找了找發(fā)生網(wǎng)上對(duì)這方面的交流很少,所以發(fā)表這篇文章拋磚引玉,希...
摘要:主要采用了原生與調(diào)用結(jié)合的功能實(shí)現(xiàn)功能。所以根據(jù)這種方法,讀者可以根據(jù)自己的需求添加更多的功能,比如在編輯框里面插入一個(gè)可以點(diǎn)擊的標(biāo)簽或者添加一個(gè)代碼塊希望能讀到此文章的讀者,能在下方一起交流,更希望大佬提出錯(cuò)誤,謝謝地址 因?yàn)橐粋€(gè)同學(xué),要做一個(gè)能加入圖片的留言板功能,類型與QQ空間留言板和百度貼吧發(fā)帖的那種形式,同時(shí)在網(wǎng)上找了找發(fā)生網(wǎng)上對(duì)這方面的交流很少,所以發(fā)表這篇文章拋磚引玉,希...
閱讀 3579·2021-08-02 13:41
閱讀 2450·2019-08-30 15:56
閱讀 1527·2019-08-30 11:17
閱讀 1186·2019-08-29 15:18
閱讀 591·2019-08-29 11:10
閱讀 2681·2019-08-26 13:52
閱讀 520·2019-08-26 13:22
閱讀 2962·2019-08-23 15:41