選中鼠標四周的筆墨

近來終究抽閑給 Saladict 完成了鼠標懸浮取詞功用,運用了較為簡約的完成體式格局,這裏分享一下道理以及坑的處置懲罰。

初嘗試

這個需求實在很早就被人提 issue 了,當時做了一番搜刮,末了嘗試了 document.caretPositionFromPoint / document.caretRangeFromPoint ,結果不太抱負。

假如看 mdn 給的例子,就會發明,它是遍歷每一個元素增加事宜的。這麼做的原因是當運用這個要領的時刻,假如鼠標指向元素空缺的處所,它會就近取位置。所以例子經由歷程給粒度更細的元素綁定來防止這個題目。但是實際上這麼做照樣不充足的,一個段落末行或許只要幾個字符,這時候空出靠近一行,也會有上面的題目。

所以當時就放置了這個功用。

靈感

直到近來,看到一個同類的開源划詞翻譯擴大 FairyDict 完成了取詞功用,遍觀賞了一番源碼

它的道理是深度優先遞歸遍歷這個元素以及其子元素,經由歷程不停探索選中地區,並與鼠標座標對比來定位確實位置。

有無發明題目,這個遍歷歷程不恰是上面 document.caretPositionFromPoint 乾的事么,那末我們只需要末了量一下鼠標是不是在取詞局限中即可。

道理

如今總結一下道理:

  1. 經由歷程 document.caretPositionFromPoint 取得鼠標所指最靠近的元素以及文本位置 offset。
  2. 找出 offset 最靠近的單詞。
  3. 經由歷程 Range 取得部份文本(單詞)的尺寸和座標。
  4. 考證鼠標此時在單詞地區局限中。
  5. 選中這個單詞。Selection 支撐直接增加 Range

完成

按道理來完成就很簡樸了。本文上按 alt 可體驗取詞結果。

/**
 * @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()
  }
}

交互

末了,假如要供應功用開關或許設置差別按鍵的話,簡樸的處置懲罰能夠參考 FairyDict 讓事宜處置懲罰空轉。但關於 mousemove 這類比較頻仍的事宜,在封閉的時刻作廢事宜監聽能夠更好一些。在 Saladict 中以至將“面板被釘住”跟“一般狀況”離開為差別的形式,這裏藉助 RxJS 來處置懲罰龐雜的邏輯,可參考源碼

    原文作者:CRIMX
    原文地址: https://segmentfault.com/a/1190000015359078
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞