一、前面的些话 本文的内容基本上是基于“区域范围对象(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
负责调查切尔诺贝利核事故对人与环境造成影响的俄科学家亚布罗科夫<span class="selected">博士指出,因福岛核电站使用的燃料较切尔诺贝利核电站多,且有反应堆使用</span>了含有高毒性的钚的燃料,因此"福岛核电站事故可能会比切尔诺贝利带来更严重的后果"。
Copy after login
上面选中状态的那些文字就可以转换成Range对象
(下面会详细讲述)。通过Range对象
你可以找到Range
的起始点和结束点,如果你实在有心,还可以删除或是复制这些内容,或是用其他文字替换,甚至是简单的HTML。
上面的例子可以说是最简单的Range对象
的例子,因为其只包含了文字。而实际上,Range对象
也是可以包含HTML代码内容的,例如下面这个示例:
<time>2011-04-12</time>
<p>据日本广播协会电视台12日报道,日本经济产业省原子能安全保安院决定将福岛第一核电站核泄漏事故等级提高至7级。这使日本核<span class="selected">泄漏事故等级与苏联切尔诺贝利核电站核泄漏事故等级相同。</p>
<p>负责调查切尔诺贝</span>利核事故对人与环境造成影响的俄科学家亚布罗科夫博士指出,因福岛核电站使用的燃料较切尔诺贝利核电站多,且有反应堆使用了含有高毒性的钚的燃料,因此"福岛核电站事故可能会比切尔诺贝利带来更严重的后果"。</p>
Copy after login
同样的,Range对象
被创建,且包含HTML,现在的问题是选择的内容正好跨过了楚河和汉界(跨标签),如果就单纯的论选择的内容的话,应该如下:
泄漏事故等级与苏联切尔诺贝利核电站核泄漏事故等级相同。</p>
<p>负责调查切尔诺贝
Copy after login
显然,上面的HTML属于1级残废,基本无效。然而幸运的是,所有的浏览器都会自动调整HTML片段使其有效,就像变成下面这样:
泄漏事故等级与苏联切尔诺贝利核电站核泄漏事故等级相同。</p>
<p>负责调查切尔诺贝
Copy after login
可以看到,浏览器自动补全了一定数目的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. 总的兼容性
Instructions:
-
W3C Range对象
is the only official designation. Basically it treats Range
as a document fragment containing the DOM.
-
Mozilla Selection对象
is somewhat redundant and exists for backward compatibility with Netscape 4. It is similar to W3C Range对象
and is also based on the DOM tree.
-
Microsoft Text Range对象
The difference between Guo Degang and Xuan Bin is the above two, because it is based on strings. In fact, it is difficult for the string contained in Text Range
to jump into a DOM node all at once.
In general, Mozilla Selection对象
is just a mess. The only shining point is that it can directly turn any content selected by the user into a complete Range对象
and some additional methods or attributes can be backward compatible with Netscape 4. . But unfortunately other browsers except IE support this Selection对象
.
4. Obtain user-selected content
No need for mother-in-law’s explanation, just look at the relevant code:
var userSelection;
if (window.getSelection) { //Modern browsers
userSelection = window.getSelection();
} else if (document.selection) { //IE browser considers To Opera, it should be placed at the end
userSelection = document.selection.createRange();
}
Due to compatibility issues, IE browser takes advantage of IE and other browsers Eat Mozilla steamed buns.
The userSelection above is a Selection object under Mozilla, Safari, and Opera, while it is a Text Range object under IE. This difference will affect your subsequent scripts: Internet Explorer's Text Ranges are completely different from Mozilla's Selection or W3C's Range objects. You need to write two different sets of scripts for IE and other browsers.
You need to pay attention to the order of script writing: Mozilla Selection needs to be placed first. The reason is that Opera supports two kinds of objects. If you use window.getSelection() to read the content selected by the user, Opera will create a Selection object; while using document.selection will create a Text Range object.
Because Opera supports Mozilla Selection and W3C Range very well, but its support for Microsoft Text Range is not satisfactory. So obviously priority is given to standard browsers, i.e. using window.getSelection().
5. Contents of userSelection
The current content of the userSelection variable is either Mozilla Selection or Microsoft Text Range object. Therefore it allows access to all methods and properties defined on the object.
The Mozilla Selection object contains the text content selected by the user, as follows:
alert(userSelection)
Although the format is not a string, a pop-up similar to the following will still appear under modern browsers Content:
The level of the leakage accident is the same as the level of the nuclear leakage accident at the Chernobyl Nuclear Power Plant in the Soviet Union. Responsible for Investigating Chernobyl
To obtain the same information from a Microsoft Text Range object, you need to use userSelection.text. In order to read the rotated text, you can use code similar to the following:
var selectedText = userSelection;
if (userSelection.text) {
selectedText = userSelection.text;
}
Now selectedText contains the text selected by the user.
You can click here: Get the text demo selected by the user
For example, in the IE7 browser, select a piece of text and click the test button on the demo page, something similar to the following will appear The pop-up content:
6. Create a Range object from the Selection object Under IE browser, userSelection is Text Range. Under modern browsers, userSelection is still Selection object. To create a Range object with the same content as the Selection object, you can use code similar to the following:
var getRangeObject = function(selectionObject) {
if (selectionObject.getRangeAt)
return selectionObject.getRangeAt(0);
else { // Older version of Safari!
var range = document.createRange();
range.setStart(selectionObject.anchorNode,selectionObject.anchorOffset);
range.setEnd(selectionObject.focusNode,selectionObject.focusOffset);
return range;
}
}
var rangeObject = getRangeObject(userSelection);
Ideally, we can get the W3C Range object through the getRangeAt() method of the Selection object. This method can return the range object of the given index value. Normally, the index value of the first Range in JavaScript is 0.
Use a program to create a Range
Safari 1.3 does not support getRangeAt(), so if we want to take this browser into consideration, we need to use other methods to create a new Range object. Obviously, an object is created:
var range = document.createRange();
The above line of code creates an empty Range. In order to insert content, we need to pass setStart() and setEnd() Method defines starting and ending points.
These two methods require two parameters:
1. The starting and ending DOM nodes of the Range
2. The starting and ending text offset of the Range. The offset refers to the position of the first and last characters of the selected text in the text node.
The two parameter attributes of setStart() are startContainer and startOffset; the two parameter attributes of setEnd() are endContainer and endOffset.
Take the following example:
Men, even if they are 50 years old, they should never touch a woman who is over 26 years old and has not yet been married. She can be divorced, widowed, etc., but she can never be unmarried. If you are not married after the age of 26, this kind of woman is generally mentally abnormal, or else she has serious problems. Markets rarely make mistakes. Even if it makes a mistake, the probability of you picking up the treasure is very small.
The future changes in the marriage market will be a very interesting question, and it will also have a decisive impact on the future trend of the mainland economy. It will have a decisive impact on the distribution of the industry and the overall efficiency of the economy. .
- Why is it the exact number 26?
Here the Range starts from the second
node and ends with the first
node. (Usually the index of the first character of a text node is 0.)
Since the text offset value at the node is 8, and the offset value at the
node is 5, we have :
var startP = [the p node]; var endLi = [the second li node];
range.setStart(startP, 8);
range.setEnd(endLi, 5);
Read the start and end selected content
setStart(startContainer, startOffset) and setEnd(endContainer, endOffset) are mentioned above. Considering the actual situation, it is difficult for you to accurately know the starting position of the text selected by the user. Therefore, the above method of assigning offset values at a glance obviously has great limitations. Fortunately (see the compatibility table above) the Range object has 4 attributes used to define the starting and ending points of the selection. These 4 attributes are similar to the Selection object, but have different names: anchorNode/anchorOffset defines the selection. Start, focusNode/focusOffset definition ends.
Therefore, the above script to create a selection can be implemented using the following code:
range.setStart(selectionObject.anchorNode,selectionObject.anchorOffset);
range.setEnd(selectionObject.focusNode,selectionObject .focusOffset);
Concerns about Safari
It’s already 2011, Shi Xiaolong has been involved in scandals, and Safari 5 has been out for a while. Therefore, if you use a program to create a Range just to take into account the lower version of Safari, I think it is not necessary at all. Especially in this magical country of ours, there are very few people who use Safari first, and very few who use lower versions. Safari has long supported getRangeAt(), and so does the Chrome browser.
You can click here: getRangeAt test demo under Safari
Select any part of the text in the demo page, and then click the test button, it will be displayed under the newer version of Safari browser A result similar to the picture below appears:
So, in the current environment, if you want to convert the Selection object into a Range object, just use the following code (full version):
var userSelection, rangeObject; if (window.getSelection) {
//Modern browsers
userSelection = window.getSelection();
} else if (document.selection) {
//IE browser takes Opera into account and should be placed behind
userSelection = document .selection.createRange();
}
//Range object
rangeObject = userSelection;
if (userSelection.getRangeAt) {
//Modern browsers
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> <br>var cssApplier; <br>window.onload = function() { <br>rangy.init(); <br>cssApplier = rangy.createCssClassApplier("selectClass", true); <br>document.body.onmouseup = function() { <br>cssApplier.toggleSelection(); <br>}; <br>}; <br></script>
如果您看到下面的文字,可能是由于在其他网站或是RSS中阅读本文,本文原地址:http://www.zhangxinxu.com/wordpress/?p=1591,本文作者:张鑫旭,来自张鑫旭-鑫空间-鑫生活,访问原出处更多优秀技术文章。
八、实际的应用
微博之插入话题
差不多去年这个时候,自己折腾过JS 文本域光标处添加文字并选中的内容,也是拿的新浪微博示例的,文章是“新浪微博插入话题后部分文字选中的js实现”,但是去年这篇文章多实现的话题插入效果是比较弱的:
1. 选中普通文字不能作为话题插入
2. 话题只能插在文本域最后二不是光标处
3. 默认文字的话题可以重复插入
所以,趁这个机会,正好把微博之插入话题这个功能完善下。
您可以狠狠地点击这里:微博插入话题的效果实现demo
欢迎输入内容,点击测试。大致会有类似下面的效果(截自Chrome):
源代码有些高度,为了节约篇幅,这里就不展示出来了,您可以在demo页面中看到完整的CSS/HTML/JS代码。不过JS部分半封装,您要是有兴趣可以在外面包裹一个函数使其插件化,我是懒得再去折腾了。
九、结语相关
对于Range
相关的知识即使到现在都是半生不熟的,所以文章的内容更多的算是翻译性质的内容。自己并没有从深入理解的基础上很浅显地剖析相关知识点,文章很多地方会显得不怎么通俗易懂。
文中多展示的Range
等兼容性表格的数据都是N年前的,还是Safari 1.3时代的数据,老的牙都掉了,实用价值大打折扣,不过可以告知的是先前现代浏览器所不支持的个别属性现早就支持了。
跌跌撞撞,滚滚爬爬。文章难免有表述不准确的地方,欢迎指正。也欢迎提交相关的脚本的bug。
参考文章及相关页面:
-
Introduction to Range
-
W3C DOM Compatibility – Range
-
rangy – A cross-browser JavaScript range and selection library
-
Reveal a Background Image upon Text Selection
Original article, please indicate when reprinting it from Zhang Xinxu-Xin Space-Xin Life