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

一、前面的些话

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

本文有不少篇幅就是讲如何将用户的这种选择转换为W3C RangeMicrosoft
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内容一定是有效的。

三、浏览器的兼容性

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

支持:不支持:部分支持:

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 SelectionW3C
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页面上的测试按钮,就会有类似下面的弹出内容:

六、从Selection对象创建Range对象

在IE浏览器下,userSelectionText
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()的两个参数属性为startContainerstartOffsetsetEnd()两个参数属性为endContainerendOffset

以下面这个例子举例:

<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浏览器下就会出现类似下图的结果:

所以,在当前环境下,要想将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,个人觉得有些庞大,在实际项目中的应用价值不大

示例1,获取用户选中的文字:

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

选中部分文字点击按钮,会有如下弹出(截自Firefox3.6):

相关JavaScript代码如下:

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

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

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

选中页面上一段文字,然后失去焦点,就会看到文字后面有了个美女背景图,如下截图,截自IE7浏览器:

完整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>

八、实际的应用

微博之插入话题

差不多去年这个时候,自己折腾过JS 文本域光标处添加文字并选中的内容,也是拿的新浪微博示例的,文章是“新浪微博插入话题后部分文字选中的js实现”,但是去年这篇文章多实现的话题插入效果是比较弱的:

1. 选中普通文字不能作为话题插入

2. 话题只能插在文本域最后二不是光标处

3. 默认文字的话题可以重复插入

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

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

欢迎输入内容,点击测试。大致会有类似下面的效果(截自Chrome):

源代码有些高度,为了节约篇幅,这里就不展示出来了,您可以在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
时间: 2024-07-30 00:25:49

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

WPS Office for Mac如何修改Word文档文字排列?WPS office修改Word文档文字排列方向教程

Word文档如何改变文字的排列方向?最新版WPS Office for Mac修复了文字排版相关的细节问题,可以更快捷的进行Word编辑,WPS Office在苹果电脑中如何修改Word文档文字排列方向呢?下面小编给大家带来详细的WPS office修改Word文档文字排列方向教程,希望对大家有所帮助! https://www.macdown.com WPS Office for Mac特别注意 为保证最新版WPS Office for Mac正常使用,请在安装WPS Office 2019 f

将Word文档发给别人时如何限制别人只能修改文档部分内容

将Word文档发给别人时如何限制别人只能修改文档部分内容 转自:互联网.时间:2014-04-16   作者:snow   来源:互联网 在很多情况下我们都不希望别人修改我们的文档内容,特别实在将Word文档发给别人时,我们只希望别人能够在word文档中填写该填的地方,不该填写的地方只能看,不能修改.整个文档完成后就向填空题一样,只能在里面填入相关的内容,不能更改和编辑其他部分.想实现这样的功能并非难事,只需对文档中的部分内容设置保护即可,同样如果内容不需要保护,只需解除即可.  一.设置保护的

PDF格式文档的内容如何编辑

对于经常处理各种文档的会遇到很多不同格式的文档文件,除了office.txt等文档外,PDF这种格式的文档也是经常会遇到的,有些PDF文件是可以进行编辑的,但编辑文字没有word编辑起来方便,而且需要编辑工具来编辑,PDF的阅读工具是不能对文档内容进行编辑的,那么到底怎么编辑的呢? 先装好PDF的编辑工具,然后用PDF编辑工具来打开需要处理的PDF文档,在编辑工具的编辑区域就可以对文档的内容进行编辑修改了,需要切换页面的话直接在左侧选择对应的缩略图就可以了. 编辑的时候可以按住Ctrl+鼠标滚轮

强大的矢量图形库:Raphael JS 中文帮助文档及教程

Raphael 是一个用于在网页中绘制矢量图形的 Javascript 库.它使用 SVG W3C 推荐标准和 VML 作为创建图形的基础,你可以通过 JavaScript 操作 DOM 来轻松创建出各种复杂的柱状图.饼图.曲线图等各种图表,还可以绘制任意形状的图形,可以进行图表或图像的裁剪和旋转等复杂操作 Rapha?l 是跨浏览器的矢量图形库,目前支持的浏览器包括: Firefox 3.0+,Safari 3.0+,Chrome 5.0+,Opera 9.5+ 以及 Internet Exp

如何解决用jquery.uploadify.js进行多文档上传会修改document的title

在dwz框架中使用了jquery.uploadify.js进行多文档上传当打开窗口后会发现document的title被无意之中修改.进行代码调试也没有发现.对swfobject进行研究也没有发现修改title的情况. 只能采用一种方式禁止修改title var oldTitle = document.title; try { document.attachEvent('onpropertychange', function(){ if (document.title != oldTitle )

C# 给Word文档添加内容控件

C# 给Word文档添加内容控件 在MS Word中,我们可以通过内容控件来向word文档中插入预先定义好的模块,指定模块的内容格式(如图片.日期.列表或格式化的文本等),从而创建一个结构化的word文档.下面就来看看如何使用C#给word文档添加组合框.文本.图片.日期选取器及下拉列表等内容控件(这里我借助了一个word组件Spire.Doc). 添加组合框内容控件 组合框用于显示用户可以选择的项目列表.和下拉列表不同的是组合框允许用户编辑或添加项. //给段落添加一个内容控件并指定它的SDT

JS如何实现文档加载完成后再去执行代码

JS如何实现文档加载完成后再去执行代码:在执行某些操作的时候,需要当文档完全加载完成之后再去执行,否则可能出现意向不到的情况,先看一段代码实例: <!DOCTYPE html> <html> <head> <meta charset=" utf-8"> <meta name="author" content="http://www.51texiao.cn/" /> <title&g

Sea.js 手册与文档

Sea.js 手册与文档 首页 | 索引 目录 何为 CommonJS 何为 CommonJS 模块 为何封装模块 何为 CommonJS? CommonJS 是一个有志于构建 JavaScript 生态圈的组织.它有一个 邮件列表,有很多开发者参与其中. 整个社区致力于提高 JavaScript 程序的可移植性和可交换性,无论是在服务端还是浏览器端. 何为 CommonJS 模块? JavaScript 并没有内置模块系统(反正现在没有),于是 CommonJS 创造了自己的. 传统的 Com

js获取窗口滚动条高度、窗口可视范围高度、文档实际内容高度、滚动条离浏览器底部的高度

1.获取窗口可视范围的高度 1 //获取窗口可视范围的高度 2 function getClientHeight(){ 3 var clientHeight=0; 4 if(document.body.clientHeight&&document.documentElement.clientHeight){ 5 var clientHeight=(document.body.clientHeight<document.documentElement.clientHeight)?doc