JavaScript之Range–或許會有點用

_xiadd_發表於2018-06-02

Range是JavaScript的內建物件,一般來講用到的地方不是很多,主要是一些互動性比較強的場景可能會用到,比如高亮標註,用不到還好說,如果用到了查資料確實也是比較少的, 所以這裡總結一下筆記,不會太深入。

需要注意的是這裡很多方法都屬於實驗性功能, 所以生產環境使用的使用需要謹慎,具體可以參考MDN。這裡不贅述了。

range的應用場景

這類相對比較生僻的api應用常見並不是很多, 這裡我們先了解一下range的應用場景.

  1. 就是常見的高亮標註電子書之類的

  2. 人工標註機器學習所需的基礎文件(我所做的)

當然應該也有很多其他場景, 我也沒怎麼接觸. 有興趣的可以自行了解

Range是什麼

顧名思義,Range其實可以認為是一個選中的文字範圍, 但是Range又不依賴於滑鼠選中, 我們可以自行構造或者克隆。不過在細說Range之前我們先了解一下Selection。

如圖當我們選中一段文字時, 我們就以通過window.getSelection來獲取Selection物件

image
image

Selection可以window.getSelection().toString()直接獲取選中的文字, 但是很多時候我們並不是要獲取選中的文字,而是要得到選中文字所在位置並將其儲存起來。這時候就是Range發揮作用的地方了。

window.getSelection().getRangeAt(0)可以將一個Selection物件轉化為Range物件。

image

我們需要關注的東西是startContainer,endContainer, startOffset,endOffset。這四個屬性可以定位一段被選中的文字。

image

如上圖,我們知道Dom元素排列是一段一段的, 這裡的container就是指的每個段,offset就是選中的位置。Range肯定是連續的,這樣我們就可以定位一段完整的Range。

Range的儲存

如果作為高亮, Range必然是要存到伺服器上的, 但是作為js物件, Range不可以直接存到資料庫裡,這時候就要對Range進行一定的處理了。

上面提到過Range是可以手動建立的:document.createRange

var range = document.createRange();

range.setStart(startNode, startOffset);
range.setEnd(endNode, endOffset);
複製程式碼

這裡startNode指startContainer, 自然就是指dom元素了,看到這裡其實大家心裡也該有點方案了,不好儲存的無非就是dom元素了,那我們將dom元素轉為選擇器存起來就好了, 到時再通過選擇器獲取dom元素。

當然我們也有其他選擇: xpath, 主要是我接手專案的時候就是利用的xpath, 將dom轉為xpath的程式碼如下:

// 獲取一個元素的xpath
function getElementXPath (element) {
  if (!element) return null

  if (element.id) {
    return `//*[@id=${element.id}]`
  } else if (element.tagName === `BODY`) {
    return `/html/body`
  } else {
    const sameTagSiblings = Array.from(element.parentNode.childNodes)
      .filter(e => e.nodeName === element.nodeName)
    const idx = sameTagSiblings.indexOf(element)

    return getElementXPath(element.parentNode) +
      `/` +
      element.tagName.toLowerCase() +
      (sameTagSiblings.length > 1 ? `[${idx + 1}]` : ``)
  }
}
複製程式碼

將xpath轉化為Range:

function createRangeFromXPathRange (xpathRange) {
    var startContainer,
      endContainer,
      endOffset,
      evaluator = new XPathEvaluator()

    // must have legal start and end container nodes
    startContainer = evaluator.evaluate(
      xpathRange.startContainerPath,
      document.documentElement,
      null,
      XPathResult.FIRST_ORDERED_NODE_TYPE,
      null
    )
    if (!startContainer.singleNodeValue) {
      return null
    }

    if (xpathRange.collapsed || !xpathRange.endContainerPath) {
      endContainer = startContainer
      endOffset = xpathRange.startOffset
    } else {
      endContainer = evaluator.evaluate(
        xpathRange.endContainerPath,
        document.documentElement,
        null,
        XPathResult.FIRST_ORDERED_NODE_TYPE,
        null
      )
      if (!endContainer.singleNodeValue) {
        return null
      }

      endOffset = xpathRange.endOffset
    }

    // map to range object
    var range = document.createRange()
    range.setStart(startContainer.singleNodeValue, xpathRange.startOffset)
    range.setEnd(endContainer.singleNodeValue, endOffset)
    return range
  }
複製程式碼

總結

這篇文章筆記不會介紹太多api或者太過深入, 但是用法思路是一定的。共勉。

image

相關文章