Range是JavaScript的內建物件,一般來講用到的地方不是很多,主要是一些互動性比較強的場景可能會用到,比如高亮標註,用不到還好說,如果用到了查資料確實也是比較少的, 所以這裡總結一下筆記,不會太深入。
需要注意的是這裡很多方法都屬於實驗性功能, 所以生產環境使用的使用需要謹慎,具體可以參考MDN。這裡不贅述了。
range的應用場景
這類相對比較生僻的api應用常見並不是很多, 這裡我們先了解一下range的應用場景.
-
就是常見的高亮標註電子書之類的
-
人工標註機器學習所需的基礎文件(我所做的)
當然應該也有很多其他場景, 我也沒怎麼接觸. 有興趣的可以自行了解
Range是什麼
顧名思義,Range其實可以認為是一個選中的文字範圍, 但是Range又不依賴於滑鼠選中, 我們可以自行構造或者克隆。不過在細說Range之前我們先了解一下Selection。
如圖當我們選中一段文字時, 我們就以通過window.getSelection
來獲取Selection物件
Selection可以window.getSelection().toString()
直接獲取選中的文字, 但是很多時候我們並不是要獲取選中的文字,而是要得到選中文字所在位置並將其儲存起來。這時候就是Range發揮作用的地方了。
window.getSelection().getRangeAt(0)
可以將一個Selection物件轉化為Range物件。
我們需要關注的東西是startContainer,endContainer, startOffset,endOffset。這四個屬性可以定位一段被選中的文字。
如上圖,我們知道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或者太過深入, 但是用法思路是一定的。共勉。