前面我们主要介绍了IE下的textRange方法,以及execCommand命令。今天我们介绍其他浏览器支持的范围区域对象:Selection,Range。
首先我们先列一下Selection对象的API,翻译的MDN下的Selection对象的介绍
selection 对象代表通过用户选中的文本或者现在的光标位置。为了修改或者查看获取selection对象,可以调用window.getSelection();
一个用户创建一个selection区域可能是从左到右.也可能是从右到左。anchor代表选区的开始位置,focus代表选区的结束位置。如果你真PC端通过鼠标创建的selection
选区,那么anchor代表鼠标按下的地方,focus代表鼠标松开的地方。anchor和focus不应该被开始和结束位置混淆。因为anchor可能在focus的前面或者后面,这取决于你选中的方向。
属性:
1,Selection.anchorNode-只读
返回选区的开始节点。
2,Selection.anchorOffset-只读
返回选区的位置在开始节点中的偏移位置的数值。如果开始节点是一个文本节点,数值代表开始节点前的字符数。如果开始节点是元素节点,数值代表开始节点中光标前面有多少子元素。
3,Selection.focusNode-只读
返回选区的结束节点。
4,Selection.focusOffset-只读
同2,
5,Selection.isCollapsed-只读
返回开始点和结束点是否是同一位置。
6,Selection.rangeCount-只读
返回选区范围的个数,但兼容处理大部分浏览器只支持一个选区。
方法:1,Selection.getRangeAt(index)
返回ranges中当前选中的range对象。
2,Selection.collapse(parentNode,offset)
将当前全区合并到一个点上。
3,Selection.extend(parentNode,offset)
移动选区的结束点到指定位置。
4,Selection.modify(alter,direction,granulary)
修改当前选区。此方法处于试验阶段,不建议使用。
5,Selection.collapseToStart()
合并选区中的第一个range对象到开始位置。
6,Selection.collapseToEnd()
合并选区中的最后一个range对象到结束位置。
7,Selection.selectAllChildren()
添加指定节点的所有子元素到选区中。
8,Selection.addRange(range)
添加range到选区中。
9,Selection.removeRange(range)
从选区中删除一个range对象。
10,Selection.removeAllRanges()
删除选区中的所有ranges对象。
11,Selection.deleteFromDocument()
从文档中删除选区的内容。
12,Selection.toString()
返回选区对象中当前的字符串,IE返回当前选中的text文本。
13,Selection.containsNode(aNode,true/false)
判断一个节点是不是selection选区的一部分。
我们下面实现一个获取用户选中文本内容的方法。因为前面已经介绍了textRange的方法,所以这里的示例都采用兼容代码。
....
获取选中文本
function getText(){
if(window.getSelection){
var selection=window.getSelection();
console.log(selection.toString());
}else{
var rg=document.selection.createRange();
console.log(rg.text);
}
}
注:这里解释一下IE的textRange,必须用document.selection.createRange(),不能用document.body.createTextRange();因为selection的createRange是在选区上创建的textRange,依据于用户选中内容,而body.createTextRange依据的是body,默认的是全选状态,这个你可以自己执行一下select()方法可以看到全选效果。你可以点击这里查看文内红字补充的两者的区别。
之前我们在“复制”功能中用selection.selectAllChildren(dom)方法实现过全选的功能,这里我们用selection的其他API再来实现一下全选功能。function selectAlls(){
var dom=document.getElementById("tt");
if(window.getSelection){
var selection=window.getSelection();
var startNode=dom,endNode=dom;
if(dom.childNodes.length>0){
startNode=dom.firstChild;
var tempNode=dom.lastChild;
while(tempNode.nodeType==1&&tempNode.childNodes.length>0){
tempNode=tempNode.lastChild;
console.log(tempNode);
}
endNode=tempNode;
}
selection.collapse(startNode,0);
selection.extend(endNode,endNode.length);
}else{
document.selection.clear();
var rg2=document.selection.createRange();
rg2.moveToElementText(dom);
rg2.select();
}
}
给此段代码实现了一个demo,什么都没有看到的直观。点击这里查看兼容的复制功能。
这段代码中我们使用了collapse和extend方法,两个方法的参数一样,第一个是要移动到的dom节点,第二个是偏移量,如果是dom节点,那么偏移量就是其子元素的个数,如果是textNode那么偏移量就是其字符数。collapse移动的是开始点,所以我们就移动到一个子元素的0偏移位置。不用遍历,即使子元素还有子节点那也是在开始位置。而通过extend方法移动的是focus结束点的位置。所以我们要通过while循环得到最内层的文本节点,默认也是在节点的开始位置,向后移动其字符个数。就能实现全选了。IE9+支持window.getSelection方法操作W3C的range对象,并且IE9+的浏览器的childNodes属性是包含文本节点的。
关于属性兼容性的介绍你可以查看这里。
下面我们列一下W3C的Range对象的API:翻译自MDN。
Range对象代表包含节点和部分文本节点的document文档片段。
Range对象可以通过Document.createRange()方法创建,也可以Selection.getRangeAt()方法或者Document.createRangeAtPoint()方法创建。
还有一个Range()构造器可以创建Range对象。
属性:
1,Range.collapsed-只读
返回一个boolean值表示Range的开始和结束点是否在同一个位置。
2,Range.commonAncestorContainer-只读
返回包含startContainer(开始节点)和endContainer(结束节点)最内层的dom元素,相当于IE的range.parentElement对象。
3,Range.endContainer-只读
返回Range的结束节点。
4,Range.endOffset-只读
返回Range位置距离结束节点的偏移量。
5,Range.startContainer-只读
返回Range的开始节点。
6,Range.startOffset-只读
返回Range位置距离开始节点的偏移量。
构造方法:
Range()
返回一个以全局Document作为开始和结束的Range对象。
方法:
1,Range.setStart(startNode,startOffset)
设置Range的开始位置。
2,Range.setEnd(endNode,endOffset)
设置Range的结束位置。
3,Range.setStartBefore(refNode)
设置Range的开始位置在引用元素的前面。
4,Range.setStartAfter(refNode)
设置Range的开始位置在引用元素的后面。
5,Range.setEndBefore(refNode)
同上
6,Range.setEndAfter(refNode)
同上
7,Range.selectNode(refNode)
选中一个元素
8,Range.selectNodeContents(refNode)
选中元素的内容。
9,Range.collapse(true/false);
折叠Range的范围到一点,true为开始位置,false为结束位置,默认为false。
10,Range.cloneContents();
返回Range中的节点的一个DocumentFragment的文档片段对象。
11,Range.deleteContents();
从文档中删除Range对象的内容。
12,Range.extractContents();
把文档中的Range内容移动到DocumentFragment对象中并返回,相当于cloneContents和deleteContents的合并使用。
13,Range.insertNode(newNode);
在Range的开始位置插入一个元素。
14,Range.surroundContents(newNode);
在Range外层包围一个newNode元素,跟jquery的wrap方法相似。
15,Range.compareBoundaryPoints(how,sRange);
比较两个Range对象的范围,返回-1,0,1表示在前面,相等,在后面。
how的可选值为:Range.END_TO_END,Range.END_TO_START,Range.START_TO_END,Range.START_TO_START.
表示比较的方式,END_TO_END就是结束点和结束点比较,其他雷同。
16,Range.cloneRange();
返回Range对象的一个副本,跟IE下的range.duplicate()方法功能一样。
17,Range.detach()
释放Range对象,释放内存以提高性能,用了此方法,就不能再对Range进行其他的操作了。
18,Range.toString()
返回Range对象的文本内容。
19,Range.comparePoint(refNode,offset)
比较位置点和Range对象的关系,返回-1,0,1表示在之前,其内,其后。
20,Range.createContextualFragment(tagstring)
根据传入的字符串创建并返回一个DocumentFragment对象,字符串里面可以含有标签。
21,Range.intersectsNode(refNode)
比较引用元素是否和Range对象范围相交,返回boolean值。
22,Range.isPointInRange(refNode,offset)
判断给定点是否中Range范围内。
Range对象有兼容性问题,贴一张MDN的兼容性。
下面我们利用Range对象的API实现一个插入hr横线的功能。插入横线
//判断一个元素及其父元素中是否是设计模式的元素。
function isInedit(pat){
var flag=false;
while(pat.nodeType!=9){
if(pat.nodeType==1){
var isedit=pat.getAttribute("contenteditable");
if(isedit){
flag=true;
break;
}
}
pat=pat.parentNode;
}
return flag;
}
}
function insertHR(){
var dom=document.getElementById("tt");
if(window.getSelection){
var selection=window.getSelection();
if(selection.rangeCount<=0){
return ;
}
var rng=selection.getRangeAt(0);
var pat=rng.commonAncestorContainer;
var flag=isInedit(pat);
if(!flag){
return;
}
rng.insertNode(document.createElement("hr"));
}else{
var rg=document.selection.createRange();
var pat=rg.parentElement();
var flag=isInedit(pat);
if(!flag){
return;
}
rg.pasteHTML("
");
}
代码看着不直观,来个DEMO,点击这里体验插入横线的功能。
上面这段代码我们用到了Range的commonAncestorContainer方法获取Range最近的父元素,然后要确定他在contenteditable元素内,之后通过insertNode来插入HR元素。代码很简短很好理解,事件要用mousedown的事件,用click事件的话,range对象早已转移到你点击的按钮上了。
注:再提一点,如果没有光标或选中时,getRangeAt会报错,所以要先判断rangeCount.如果用document.createRange()方法创建的对象操作是看不到效果的,一定记得要用Selection方法的addRange把创建的Range对象添加到selection对象中,才能在页面看到效果,此过程如同IE下的select()方法的调用。
结尾:连续介绍了几天textRange,Range,以及selection对象的api以及使用方法,写了几个demo来帮助大家理解。我们学习这些浏览器中最复杂的东西,不是为了要去实现一个富文本编辑器,富文本编辑器已经很多,还不停的造轮子,没有什么太大的价值了。但是我们掌握range后就可以去实现一些很玄的功能,比如一些论坛上的代码复制功能,等等。