JS Range HTML文档/文字内容选中、库及应用介绍


Posted in Javascript onMay 12, 2011

一、前面的些话
本文的内容基本上是基于“区域范围对象(Range objects)”这个概念来说的。这个玩意,可以让你选择HTML文档的任意部分,并可以拿这些选择的信息做你想做的事情。其中,最常见的Range是用户用鼠标选择的内容(user selection)。

本文有不少篇幅就是讲如何将用户的这种选择转换为W3C Range或Microsoft Text Range对象。

二、什么是Range?
所谓"Range",是指HTML文档中任意一段内容。一个Range的起始点和结束点位置任意,甚至起始点和结束点可以是一样的(也就是空Range)。最常见的Range是用户文本选择范围(user text selection)。当用户选择了页面上的某一段文字后,你就可以把这个选择转为Range。当然,你也可以直接用程序定义Range。

例如下面这个模样的例子:

2011-04-12 
负责调查切尔诺贝利核事故对人与环境造成影响的俄科学家亚布罗科夫博士指出,因福岛核电站使用的燃料较切尔诺贝利核电站多,且有反应堆使用了含有高毒性的钚的燃料,因此"福岛核电站事故可能会比切尔诺贝利带来更严重的后果"。

上面选中状态的那些文字就可以转换成Range对象(下面会详细讲述)。通过Range对象你可以找到Range的起始点和结束点,如果你实在有心,还可以删除或是复制这些内容,或是用其他文字替换,甚至是简单的HTML。

上面的例子可以说是最简单的Range对象的例子,因为其只包含了文字。而实际上,Range对象也是可以包含HTML代码内容的,例如下面这个示例:

<time>2011-04-12</time> 
<p>据日本广播协会电视台12日报道,日本经济产业省原子能安全保安院决定将福岛第一核电站核泄漏事故等级提高至7级。这使日本核泄漏事故等级与苏联切尔诺贝利核电站核泄漏事故等级相同。</p> 
<p>负责调查切尔诺贝利核事故对人与环境造成影响的俄科学家亚布罗科夫博士指出,因福岛核电站使用的燃料较切尔诺贝利核电站多,且有反应堆使用了含有高毒性的钚的燃料,因此"福岛核电站事故可能会比切尔诺贝利带来更严重的后果"。</p>

同样的,Range对象被创建,且包含HTML,现在的问题是选择的内容正好跨过了楚河和汉界(跨标签),如果就单纯的论选择的内容的话,应该如下:

泄漏事故等级与苏联切尔诺贝利核电站核泄漏事故等级相同。</p> 
<p>负责调查切尔诺贝

显然,上面的HTML属于1级残废,基本无效。然而幸运的是,所有的浏览器都会自动调整HTML片段使其有效,就像变成下面这样:

<p>泄漏事故等级与苏联切尔诺贝利核电站核泄漏事故等级相同。</p> 
<p>负责调查切尔诺贝</p>

可以看到,浏览器自动补全了一定数目的HTML来让Range有效。如果你复制或是移动Range,你所复制或移动的HTML内容一定是有效的。

如果您看到下面的文字,可能是由于在其他网站或是RSS中阅读本文,本文原地址:http://www.zhangxinxu.com/wordpress/?p=1591,本文作者:张鑫旭,来自张鑫旭-鑫空间-鑫生活,访问原出处更多优秀技术文章。

三、浏览器的兼容性

在真正操刀JavaScript之前我们需要大致知道Range对象的浏览器兼容性情况。实际上,问题是比较麻烦的,因为至少有3种类似Range对象,且你有必要全部理解。先展示详细的兼容性情况表:

支持:JS Range HTML文档/文字内容选中、库及应用介绍不支持:JS Range HTML文档/文字内容选中、库及应用介绍部分支持:JS Range HTML文档/文字内容选中、库及应用介绍

1. W3C Range
W3C Range
  Explorer 6/7 Firefox 2 Safari 1.3 Opera 9
cloneContents()
cloneRange()
collapse() tbd tbd tbd tbd
collapsed
commonAncestorContainer
compareBoundaryPoints()
comparePoint() ? Mozilla 扩展
createContextualFragment() ? Mozilla 扩展
deleteContents()
detach()
endContainer
endOffset
extractContents()
insertNode()
isPointInRange() ? Mozilla 扩展
selectNode()
selectNodeContents()
setEnd()
setEndAfter()
setEndBefore()
setStart()
setStartAfter()
setStartBefore()
startContainer
startOffset
surroundContents()

说明:
cloneContents()的用法类似docFrag = rangeObject.cloneContents()Range对象内容被克隆同时被添加到文档片段上,并返回自身。但是在Safari下有个问题,即如果选择范围是空,将会返回null而不是空的文档片段。可以通过类似docFrag = rangeObject.cloneContents() || document.createDocumentFragment()这样的代码修复。

deleteContents()处,Range内容会被永久删除,无返回值。

endContainer指用户选择内容结尾处的容器节点。通常是文本节点。

extractContents()用法docFrag = rangeObject.extractContents()。从DOM树上剪切Range对象并返回文档片段。该片段可以粘贴到页面上。

startContainer指用户选择内容起始处的容器节点。通常是文本节点。

startOffset在Opera浏览器下,在选择内容为空的时候返回0

2. Mozilla Selection
Mozilla Selection
  Explorer 6/7 Firefox 2 Safari 1.3 Opera 9
addRange()
anchorNode
anchorOffset
collapse() tbd tbd tbd tbd
collapseToEnd()
collapseToStart()
containsNode()
deleteFromDocument()
extend()
focusNode
focusOffset
getRangeAt()
isCollapsed
rangeCount
removeAllRanges()
removeRange()
selectAllChildren()
selectionLanguageChange()

说明:
anchorNode用法为userSelection.anchorNode。指用户选择内容起始处的容器节点。通常是文本节点。

anchorNode在Opera浏览器下,在选择内容为空的时候返回0

focusNode用法为userSelection.focusNode。指用户选择内容结尾处的容器节点。通常是文本节点。

focusOffset在Opera浏览器下,在选择内容为空的时候返回0

getRangeAt()用法为rangeObject = userSelection.getRangeAt(0),作用是将Mozilla Selection转换为W3C Range

3. Microsoft TextRange
Microsoft TextRange
  Explorer 6/7 Firefox 2 Safari 1.3 Opera 9
boundingHeight
boundingLeft
boundingTop
boundingWidth
collapse() tbd tbd tbd tbd
compareEndPoints()
duplicate()
expand()
findText()
htmlText
move()
moveEnd()
moveStart()
moveToElementText()
moveToPoint()
offsetLeft
offsetTop
parentElement()
pasteHTML()
scrollIntoView()
select()
text

说明:
htmlText用法为htmlString = userSelection.htmlText。返回字符串,为TextRange的HTML内容,相当于innerHTML。只读。

pasteHTML(),当粘贴HTML到一个文本节点时,该文本节点自动分隔。

text用法为string = userSelection.text。返回字符串,为TextRange的文本内容,相当于innerText。可读/写。

4. 总的兼容性
总的兼容性
  Explorer 6/7 Firefox 2 Safari 1.3 Opera 9
W3C Range 详述
Mozilla Selection 详述
Microsoft Text Range 详述

说明:

  • W3C Range对象是唯一官方指定。基本上其是将Range作为包含DOM的文档片段。
  • Mozilla Selection对象显得有些多余,其存在是为了向后兼容Netscape 4。其类似于W3C Range对象,也是基于DOM树的。
  • Microsoft Text Range对象跟上面两个就是郭德纲和玄彬的区别了,因为其是基于字符串的。事实上,Text Range包含的字符串是很难一下子跳变成DOM节点的。

总的来说,Mozilla Selection对象就是个打酱油的命,仅有的闪光点能够直接将用户选择任何内容变成完全Range对象以及一些额外的方法或是属性可以向后兼容Netscape 4。但是不幸的是除了IE浏览器外的其他浏览器都支持此Selection对象

四、获得用户选择内容

婆婆妈妈的解释就免了,直接看相关代码:

var userSelection; 
if (window.getSelection) { //现代浏览器 
userSelection = window.getSelection(); 
} else if (document.selection) { //IE浏览器 考虑到Opera,应该放在后面 
userSelection = document.selection.createRange(); 
}

由于兼容性的问题,IE浏览器吃IE的包子,其他浏览器吃Mozilla的馒头。

在Mozilla、Safari、Opera下上面的userSelection是个Selection对象,而在IE下则是Text Range对象。这种差异会影响到你后面的脚本:Internet Explorer的Text Ranges完全不同于Mozilla的Selection或是W3C的Range对象,你需要分别为IE和其他浏览器写两套不同的脚本。

需要注意脚本书写的顺序:Mozilla Selection需放在前面。原因在于Opera支持两种对象,如果你使用window.getSelection()去读取用户选择的内容,Opera会创建一个Selection对象;而使用document.selection则会创建一个Text Range对象。

因为Opera支持Mozilla Selection和W3C Range非常好,但是其对Microsoft Text Range的支持却差强人意。所以显然优先考虑标准浏览器,即使用window.getSelection()。

五、userSelection的内容
userSelection变量现在的内容要么是Mozilla Selection要么就是Microsoft Text Range对象。因此它允许访问定义在对象上的全部方法和属性。

Mozilla Selection对象包含用户选择的文本内容,如下操作:

alert(userSelection)
虽然格式并不是字符串,但是在现代浏览器下还是会弹出类似下面的内容:

泄漏事故等级与苏联切尔诺贝利核电站核泄漏事故等级相同。负责调查切尔诺贝
为了从Microsoft Text Range 对象上获得同样的信息,你需要使用userSelection.text。为了读取旋转的文字,可以使用类似下面代码:

var selectedText = userSelection; 
if (userSelection.text) { 
selectedText = userSelection.text; 
}

现在selectedText就包含了用户所选中的文字了。

您可以狠狠地点击这里:获取用户所选文字demo

例如在IE7浏览器下,选中一段文字再点击demo页面上的测试按钮,就会有类似下面的弹出内容:
JS Range HTML文档/文字内容选中、库及应用介绍
六、从Selection对象创建Range对象
在IE浏览器下,userSelection是Text Range,在现代浏览器下,userSelection仍然是Selection对象,要想同样创建和Selection对象内容一样的Range对象可以使用类似下面代码:

var getRangeObject = function(selectionObject) { 
if (selectionObject.getRangeAt) 
return selectionObject.getRangeAt(0); 
else { // 较老版本Safari! 
var range = document.createRange(); 
range.setStart(selectionObject.anchorNode,selectionObject.anchorOffset); 
range.setEnd(selectionObject.focusNode,selectionObject.focusOffset); 
return range; 
} 
} 
var rangeObject = getRangeObject(userSelection);

理想情况下,我们通过Selection对象的getRangeAt()方法就可以得到W3C Range对象。此方法可以返回给定索引值的range对象。通常情况下,在JavaScript中第一个Range的索引值是0。

使用程序创建Range

Safari 1.3不支持getRangeAt(),因此我们要想兼顾此浏览器,需要使用其他的方法创建新的Range对象。显然,显示创建一个对象:

var range = document.createRange();
上面的一行代码创建了一个空的Range,为了插入内容,我们需要通过setStart()和setEnd()方法定义起止点。

这两个方法需要两个参数:
1. Range起止的DOM节点
2. Range起止的文本偏移。该偏移指选中文字第一个字符和最后一个字符在文本节点中的位置。

setStart()的两个参数属性为startContainer和startOffset;setEnd()两个参数属性为endContainer和endOffset。

以下面这个例子举例:

<p>男人,即使到了50岁,也千万不要碰超过26岁还没有结婚的女人。她可以是离婚,丧偶等等的,但是绝对不能是没有结婚。超过了26岁没有结婚,这种女人一般心理变态,不然就是有严重问题。市场很少犯错。即使它犯了错,那被你捡到宝的概率也很小。</p>
<p>婚姻市场未来的变化将会是很有趣的问题,而且对未来大陆经济的走势也有举足轻重的影响,对于行业的分布,经济的整体效率有决定性的影响。</p>

<ol>
<li>为什么是26这个准确的数字?</li>
<li>找骂帖</li>
<li>言论是对的,在100年前,lz穿越了而已。</li>
</ol>此处Range开始于第二个<p>节点,结束与第一个<li>节点。(通常文本节点的第一个字符的索引是0。)

由于<p>节点处的文字偏移值是8, <li>节点处的偏移是5,因而有:

var startP = [the p node]; 
var endLi = [the second li node]; 
range.setStart(startP, 8); 
range.setEnd(endLi, 5);

读取起止选中内容

上面提到了setStart(startContainer, startOffset)以及setEnd(endContainer, endOffset)。考虑到实际情况,你很难准确知道用户选择的文字的起始位置,所以,上面一板一眼赋予偏移值的方法显然有很大的局限性。好在任何(看参见上面的兼容性表格)Range对象有4个属性是用来定义选择内容起止点的,这4个属性与Selection对象相似,但是却是不同的名称:anchorNode/anchorOffset定义选择的起始,focusNode/focusOffset定义结束。

因此,上面的脚本创建选区可以使用如下代码实现:

range.setStart(selectionObject.anchorNode,selectionObject.anchorOffset);
range.setEnd(selectionObject.focusNode,selectionObject.focusOffset);

Safari的多虑

现在已经是2011年了,释小龙都有绯闻了,Safari 5已经出来好些日子了。所以,如果仅仅是为了兼顾低版本的Safari而去使用程序创建Range,我觉得是一点必要都没有。尤其在我们这个神奇的国度上,首先使用Safari就少,低版本的就少之又少,Safari老早就已经支持getRangeAt()了,Chrome浏览器也是如此。

您可以狠狠地点击这里:Safari下getRangeAt测试demo

选择demo页面中的任意一部分文字,然后点击测试按钮,在较新版本的Safari浏览器下就会出现类似下图的结果:
JS Range HTML文档/文字内容选中、库及应用介绍
所以,在当前环境下,要想将Selection对象转换成Range对象,直接如下代码就OK了(完整版):

var userSelection, rangeObject; 
if (window.getSelection) { 
//现代浏览器 
userSelection = window.getSelection(); 
} else if (document.selection) { 
//IE浏览器 考虑到Opera,应该放在后面 
userSelection = document.selection.createRange(); 
} //Range对象 
rangeObject = userSelection; 
if (userSelection.getRangeAt) { 
//现代浏览器 
rangeObject = userSelection.getRangeAt(0); 
}

七、rangy ? JavaScript Range&Selection库
项目地址:http://code.google.com/p/rangy/

就在几天前,rangy更新到了版本1.1,作者还新更新了四五个示意的页面,展示了相关的API,方法和属性等。虽然如此,由于实例较少,还是让人很难知道此JavaScript库如何使用。这里就举几个简单的例子示意下。//zxx:此插件非压缩达115K,个人觉得有些庞大,在实际项目中的应用价值不大
JS Range HTML文档/文字内容选中、库及应用介绍
示例1,获取用户选中的文字:

您可以狠狠地点击这里:rangy获取用户选中文字demo

选中部分文字点击按钮,会有如下弹出(截自Firefox3.6):
JS Range HTML文档/文字内容选中、库及应用介绍
相关JavaScript代码如下:

var sel = rangy.getSelection(); 
alert(sel.toString());

示例2,给选中文字添加背景

您可以狠狠地点击这里:文字选中添加背景图demo

选中页面上一段文字,然后失去焦点,就会看到文字后面有了个美女背景图,如下截图,截自IE7浏览器:
JS Range HTML文档/文字内容选中、库及应用介绍
完整JavaScript代码如下:

<script type="text/javascript" src="http://www.zhangxinxu.com/study/201104/rangy/rangy-core.js"></script> 
<script type="text/javascript" src="http://www.zhangxinxu.com/study/201104/rangy/rangy-cssclassapplier.js"></script> 
<script> 
var cssApplier; 
window.onload = function() { 
rangy.init(); 
cssApplier = rangy.createCssClassApplier("selectClass", true); 
document.body.onmouseup = function() { 
cssApplier.toggleSelection(); 
}; 
}; 
</script>

如果您看到下面的文字,可能是由于在其他网站或是RSS中阅读本文,本文原地址:http://www.zhangxinxu.com/wordpress/?p=1591,本文作者:张鑫旭,来自张鑫旭-鑫空间-鑫生活,访问原出处更多优秀技术文章。
八、实际的应用
微博之插入话题

差不多去年这个时候,自己折腾过JS 文本域光标处添加文字并选中的内容,也是拿的新浪微博示例的,文章是“新浪微博插入话题后部分文字选中的js实现”,但是去年这篇文章多实现的话题插入效果是比较弱的:
1. 选中普通文字不能作为话题插入
2. 话题只能插在文本域最后二不是光标处
3. 默认文字的话题可以重复插入

所以,趁这个机会,正好把微博之插入话题这个功能完善下。

您可以狠狠地点击这里:微博插入话题的效果实现demo

欢迎输入内容,点击测试。大致会有类似下面的效果(截自Chrome):
JS Range HTML文档/文字内容选中、库及应用介绍

源代码有些高度,为了节约篇幅,这里就不展示出来了,您可以在demo页面中看到完整的CSS/HTML/JS代码。不过JS部分半封装,您要是有兴趣可以在外面包裹一个函数使其插件化,我是懒得再去折腾了。

九、结语相关

对于Range相关的知识即使到现在都是半生不熟的,所以文章的内容更多的算是翻译性质的内容。自己并没有从深入理解的基础上很浅显地剖析相关知识点,文章很多地方会显得不怎么通俗易懂。

文中多展示的Range等兼容性表格的数据都是N年前的,还是Safari 1.3时代的数据,老的牙都掉了,实用价值大打折扣,不过可以告知的是先前现代浏览器所不支持的个别属性现早就支持了。

跌跌撞撞,滚滚爬爬。文章难免有表述不准确的地方,欢迎指正。也欢迎提交相关的脚本的bug。

参考文章及相关页面:
  1. Introduction to Range
  2. W3C DOM Compatibility ? Range
  3. rangy ? A cross-browser JavaScript range and selection library
  4. Reveal a Background Image upon Text Selection

原创文章,转载请注明来自张鑫旭-鑫空间-鑫生活

Javascript 相关文章推荐
基于jquery点击自以外任意处,关闭自身的代码
Feb 10 Javascript
jquery实现简单的拖拽效果实例兼容所有主流浏览器(优化篇)
Jun 28 Javascript
javascript与jquery中的this关键字用法实例分析
Dec 24 Javascript
javascript创建对象的3种方法
Nov 02 Javascript
JavaScript实现的选择排序算法实例分析
Apr 14 Javascript
详解在express站点中使用ejs模板引擎
Sep 21 Javascript
JS实现标签滚动切换效果
Dec 25 Javascript
使用express搭建一个简单的查询服务器的方法
Feb 09 Javascript
如何用input标签和jquery实现多图片的上传和回显功能
May 16 jQuery
Vue+mui实现图片的本地缓存示例代码
May 24 Javascript
vue项目使用.env文件配置全局环境变量的方法
Oct 24 Javascript
vue界面发送表情的实现代码
Sep 11 Javascript
JavaScript中的几个关键概念的理解-原型链的构建
May 12 #Javascript
Jqyery中同等与js中windows.onload的应用
May 10 #Javascript
JQuery 1.6发布 性能提升,同时包含大量破坏性变更
May 10 #Javascript
JavaScript中为元素加上name属性的方法
May 09 #Javascript
JavaScript 放大镜 移动镜片效果代码
May 09 #Javascript
JavaScript 放大镜 放大倍率和视窗尺寸
May 09 #Javascript
关于JavaScript的with 语句的使用方法
May 09 #Javascript
You might like
php读取远程gzip压缩网页的方法
2014/12/29 PHP
PHP简单实现解析xml为数组的方法
2018/05/02 PHP
TP5框架实现一次选择多张图片并预览的方法示例
2020/04/04 PHP
web开发人员学习jQuery的6大理由及jQuery的优势介绍
2013/01/03 Javascript
JavaScript控制table某列不显示的方法
2015/03/16 Javascript
Jquery中$.post和$.ajax的用法小结
2015/04/28 Javascript
JS非Alert实现网页右下角“未读信息”效果弹窗
2015/09/26 Javascript
NodeJS整合银联网关支付(DEMO)
2016/11/09 NodeJs
javascript和jQuery中的AJAX技术详解【包含AJAX各种跨域技术】
2016/12/15 Javascript
js 轮播效果实例分享
2016/12/28 Javascript
Bootstrap导航简单实现代码
2017/03/06 Javascript
jQuery在header中设置请求信息的方法
2017/03/06 Javascript
layui导航栏实现代码
2017/05/19 Javascript
详解React 在服务端渲染的实现
2017/11/16 Javascript
Vue的elementUI实现自定义主题方法
2018/02/23 Javascript
nodejs多版本管理总结
2018/04/03 NodeJs
bootstrap实现点击删除按钮弹出确认框的实例代码
2018/08/16 Javascript
[51:07]VGJ.S vs Pain 2018国际邀请赛小组赛BO2 第一场 8.17
2018/08/20 DOTA
跨平台python异步回调机制实现和使用方法
2013/11/26 Python
python调用Delphi写的Dll代码示例
2017/12/05 Python
python opencv读mp4视频的实例
2018/12/07 Python
python读取图片任意范围区域
2019/01/23 Python
python opencv 检测移动物体并截图保存实例
2020/03/10 Python
windows10在visual studio2019下配置使用openCV4.3.0
2020/07/14 Python
原生canvas制作画图小工具的踩坑和爬坑
2020/06/09 HTML / CSS
小小的船教学反思
2014/02/21 职场文书
会计岗位职责模板
2014/03/12 职场文书
小学生竞选班干部演讲稿
2014/04/24 职场文书
孝敬父母的演讲稿
2014/05/14 职场文书
干部鉴定材料
2014/05/18 职场文书
诚实守信主题班会
2015/08/13 职场文书
行为习惯主题班会
2015/08/14 职场文书
2016暑期社会实践心得体会范文
2016/01/14 职场文书
mysql查询的控制语句图文详解
2021/04/11 MySQL
使用golang编写一个并发工作队列
2021/05/08 Golang
MongoDB连接数据库并创建数据等使用方法
2021/11/27 MongoDB