DOM的范围学习笔记
难得周末没事干,又加上大热天的,在宿舍实在难受,于是,一大早起床,就拿着电脑和JavaScript的红宝书跑去了图书馆了.
自己一直很喜欢JavaScript的各种灵活与优雅,但是很多时候都只是学习他的那些语言特性,很少去认真学习使用JavaScript的DOM编程,于是今天决定要好好看看红宝书里关于DOM的章节.前面几章都看得挺顺畅的.也算是好好重新熟悉了一下各种的DOM特性,同时,深深的感觉到,IE真恶心.Nicholas在讲到DOM的这部分时,处处都充满的IE不兼容的各种提示,以及解决这种问题的方法.我决定也得找个时间,把这些解决办法写下来,再加上一些日常的使用的函数封装成一个自己的库,方便以后自己使用.
讲回正题,当我看到DOM2的章节的时候,突然看到了这个DOM2级的范围接口,看到了上面又众多方法,却一时犯迷糊了,想不明白搞这一堆东西究竟有啥用处.于是我开始上网找各种文章,于是从博客园的另一篇旧文里找到了一点灵感,原来这玩意可以实现一些代码高亮或者选择文本高亮的东西.马上想起以前某位师兄曾经让我去写一个Chrome插件可以提供高亮笔记的功能,于是,马上动手试一下.
具体的这些接口我就不做介绍了,这些都可以在上面那篇旧文或者从红宝书上找到.
首先,上网查了一下如何获得当前页面中被选中的文本,找到了window对象中,有个getSelection()的方法,于是,马上在Chrome的console里测试一下.发现这个方法返回的是一个DOMSelection对象,里面包含了几个对我要的功能比较有用的属性.
- baseNode与baseOffset: 这两个代表的是你当前选中的文本中,开始的位置所在的Node以及相对于这个Node开始位置的偏移量;
- extentNode与extentOffset: 这两个则时代表了当前选中的文本的结束位置所在的Node以及相对于这个Node结束位置的偏移量;
- type: 这个属性时指出你当前选中的是一段文本还是你根本没有选中任何东西只是在文本中点了一下定位了一下光标,如果你选择了一段文本,那么这个值就是Range,如果只是定位了一下光标,那么这个值这事Caret;
- getRangeAt(): 很明显,我上面都说了这个DOMSelection有一个type的值是Range,那么肯定这个东西跟Range有关,于是找到了Mozilla上面的文档,发现真的有一个getRangeAt的方法,通过这个方法就可以得到这个DOMSelection相应的Range对象了.
好了,又了这些属性之后,我想我可以写一个最简单的DEMO了.
首先,先写好一个简单的HTML文件:
1 <body> 2 <div id="myDiv">We offer high quality courses <b>from the top universities</b>, for free to <b>everyone. We</b> currently host courses from Princeton University, Stanford University, University of California, Berkeley, University of Michigan-Ann Arbor, and University of Pennsylvania. We are changing the face of education globally, and we invite you to join us</div> 3 <button onclick="highlight()">Highlight!</button> 4 </body>
为了比较清楚的展示这个过程,就用一段简单点的代码,这段代码只有一个div,div里面有一些文字,然后其中某些部分用了b标签了加粗,虽然这样在实际中不好,但这个只是演示需要.定义了一个button,点击激活我等下要展示的highlight函数.
那么highlight函数应该怎么实现了,那么既然已经有一个Range对象了,我们就可以直接使用Range对象的surroundContents()方法,通过在Range给Range范围的文本环绕一个span就可以实现我们的功能了.
function highlight() { var selection = window.getSelection(); if(selection.type === "Range") { var range = selection.getRangeAt(0); var highlightSpan = document.createElement("span"); highlightSpan.className = "highlight"; range.surroundContents(highlightSpan); } }
好吧,就那么几行代码就完成了这个功能了.这么简单.
可是正当我开心玩弄的时候,发现如果我从一个标签内部开始选择,例如从第一个b标签里的top文本开始选择,然后结束于这个b标签结束后的free这个单词时,高亮的功能就不行.看了一下控制台,发现报了一个错误:Uncaught Error: BAD_BOUNDARYPOINTS_ERR: DOM Range Exception 1. 原来虽然Range会对你选择的范围进行well-formed,即如果你对一个Range对象调用extractContents()方法把这个范围的的内容extract出来成为一个DocumentFragment对象的时候,它会自动帮你添加回缺少的标签,但是对于surroundContents这个方法,well-formed却不起作用.于是我们就要对这种情况想想办法了.
正如我刚刚说,我们可以把Range中的内容extract出来成为一个fragment对象,并且把这些内容从文档中删除,而且我们可以从selection对象中得到那个选择文本开始时所在的baseNode,那么我们就可以把这个extract出来的fragment对象放到一个高亮的span中,然后把这个span添加到这个baseNode的后面.那么整段文本看起来就和原来一样,仅仅是选中的文本加上了高亮背景.好,于是我马上试一下:
function highlight() { var selection = window.getSelection(); if(selection.type === "Range") { var range = selection.getRangeAt(0); var baseNode = selection.baseNode; var highlightSpan = document.createElement("span"); highlightSpan.className = "highlight"; var fragment = range.extractContents(); highlightSpan.appendChild(fragment); baseNode.parentNode.insertAdjacentElement("afterEnd", highlightSpan); } }
好,再次测试,这次我干脆从第一个b标签的top开始选择,直到第二个b标签的everyone之后结束,点击一下,可以,没有问题.然后在试一下在同一个标签下的选择,发现出问题了,这种情况下被选中的文档直接放到了整个这个文本Node所在的元素Node的后面.于是,显然,要对不同的情况做一些处理,加一点判断,于是我把代码改成这样:
function highlight() { var selection = window.getSelection(); if(selection.type === "Range") { var range = selection.getRangeAt(0); var baseNode = selection.baseNode; var endNode = selection.extentNode; var highlightSpan = document.createElement("span"); highlightSpan.className = "highlight"; if(baseNode !== endNode) { var fragment = range.extractContents(); highlightSpan.appendChild(fragment); baseNode.parentNode.insertAdjacentElement("afterEnd", highlightSpan); } else { range.surroundContents(highlightSpan); } } }
再次测试,Ok,没有问题.
看来这个简单的demo就这样可以了,我也对DOM范围的这个接口有了进一步的了解.当然,这可能并不是最好的解决方法,但是对于学习来说,这样已经足够了.

浙公网安备 33010602011771号