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或者太过深入, 但是用法思路是一定的。共勉。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。