Web 技术研究所

我一直坚信着,Web 将会成为未来应用程序的主流

selection对象和range对象在不同浏览器上的操作差异

  操作文本选区是一件非常麻烦的事情,因为HTML这种文本模式本身就不适合做文本选区。但是既然有功能上的需求,我们就不得不硬着头皮去做这件事。目前,实现文本选取有两个标准,一个是旧标准,IE6、7、8在使用这个旧标准,是把HTML当作纯文本来计算的。这种方法对纯文本来说是很容易操作的,但是对富文本来说就麻烦了。这种方法是通过把HTML降格为普通文本操作的,所以逻辑上存在很大问题。现在比较流行的是新标准,非IE浏览器基本都兼容,IE从IE9开始兼容。新标准的操作方法是把文本精确到节点来操作,这种方法比其旧标准的破环HTML的方法逻辑性上要强的多,而且这种方法更容易对富文本进行操作。文本选区在旧标准中称为TextRange对象,在新标准中称为Range对象,他们是基于Selection的对象。现在,我们先来说说旧标准的文本选区操作方法。
  既然是基于Selection对象的,我们就要先获取这个Selection对象,它就在document对象中,直接使用document.selection就可以找到它。它提供了createRange方法,可以创建一个TextRange对象。(创建TextRange对象的方法不止一种,其它方法本文就不介绍了)得到TextRange对象以后就可以操作选区了。看下面代码
<!DOCTYPE html>
<style>
div {width:600px;border:1px solid red;}
</style>
<div contenteditable="true" id="editor">
  金樽清酒斗十千,玉盘珍馐值万钱。<br/>
  停杯投箸不能食,拔剑四顾心茫然。<br/>
  欲渡黄河冰塞川,将登太行雪满山。<br/>
  闲来垂钓碧溪上,忽复乘舟梦日边。<br/>
  行路难!行路难!多歧路,今安在?<br/>
  长风破浪会有时,直挂云帆济沧海。<br/>
</div>
<script>
//变量初始化
var range,editor;
range=document.selection.createRange();
editor=document.getElementById("editor");
//选中editor内的所有文字
range.moveToElementText(editor);
//抛弃选区的结束位置
range.collapse();
//把当前选区的结束位置向后移动16个字符
range.moveEnd("character",16);
//在界面上选中这个选区
range.select();
</script>
  看完这段代码,你就应该明白如果选中一个文本选区了。这里值得一提的是,collapse这个方法是把结束位置抛弃掉,并不是简单的设置到开始位置。结束位置被抛弃掉以后,只要没有给它重新设置位置,它就一直都会等于开始位置。即使你修改了开始位置,结束位置还是会在修改后的开始位置上。接着看代码把,为了方便,接下来的代码我只写JS部分,HTML部分和第一个例子中的一样。 //变量初始化
var range,editor;
range=document.selection.createRange();
editor=document.getElementById("editor");
//选中editor内的所有文字
range.moveToElementText(editor);
//抛弃选区的结束位置
range.collapse();
//把当前选区的开始位置向后移动17个字符
range.moveStart("character",17);
//把当前选区的结束位置向后移动16个字符
range.moveEnd("character",16);
//在界面上选中这个选区
range.select();
  这个代码会选中第二行,你会发现moveEnd的第二个参数是16而不是17+16。这就是上面说的抛弃结束位置后在重新定义它之前会和开始位置相同。设置位置大概就是这么多内容了,接着是获取一个文本选区的位置,其实这个操作没有必要,如果你想保存一个文本选区的位置,直接保存TextRange对象就好了,没必要精确到第几个字符。不过为了某些奇葩的需求,我在这里还是介绍一下获取选区位置信息的方法。获取这个信息可没有选中位置那么容易,需要用到一个叫做setEndPoint的方法。由于TextRange对象无法直接获取数值,只能使用setEndPoint这个方法来把一个TextRange对象的开始或结束位置赋值到另一个TextRange对象上,这样我们就可以通过计算字数的方式来精确位置了。至于这个setEndPoint的具体用法,你可以参考本文最后的链接去看看官方提供的说明。下面是获取选区位置的代码。
//获取编辑器对象
var editor=document.getElementById("editor");
//鼠标事件
document.onmouseup=function(){
  //变量声明
  var st,cr,l,r;
  //创建一个文本选区,并把光标设置到editor的开始位置
  st=document.selection.createRange();
  st.moveToElementText(editor);
  st.collapse();
  //创建选区对象(默认会选中当前区域)
  cr=document.selection.createRange();
  //把st的end设置成cr的start
  st.setEndPoint("EndToStart",cr);
  //开始位置就是st的文本长度了
  l=st.text.length
  //结束位置就是开始位置加上区间的文本长度
  r=l+cr.text.length
  //输出位置信息
  alert("开始位置:"+l+"\n"+"结束位置:"+r);
};
  知道了以上这些,在低版本浏览器中操作文本选区应该就基本没啥问题了。接着来说说新版浏览器的Range对象。
  首先是Selection这个对象,它不像旧标准那样是一个document的属性了,你必须用getSelection这个方法去获取,这个方法是在window对象上的,所以我们可以直接使用它。而新标准的Range对象和旧标准的区别很大,不是从Selection对象创建的,而是从document对象创建的,你可以使用document.createRange来创建一个新选区。新标准的Selection对象和旧标准的也有很大差别,与其说是差别,不如说是完全两个不同的概念。新标准的Selection对象是可以同时包含多个Range对象的,用getRangeAt方法去获取具体哪个对象,参数是Range对象组的下标。接着我们来实现一下选中第二行文字的功能。看代码
//变量初始化
var editor,selection,range,s;
editor=document.getElementById("editor");
s=editor.childNodes;
selection=getSelection();
range=document.createRange();
//设置range的开始和结束点
range.setStart(s[2],0);
range.setEnd(s[3],0);
//移除selection中原有的所有range
selection.removeAllRanges();
//把这个新的range添加到selection中
selection.addRange(range);
  如果你用旧标准的眼光去看新标准的代码你一定会一头雾水。标准的Range对象,可以说是非常非常精确,精确的让你抓狂。它精确到文本节点的第几个字符,而且还包括空白字符。所以Range对象计算起来非常费劲,但是它的逻辑很完整,完全没有破坏HTML原本的格式,不像旧标准的TextRange对象,为了方便计算把HTML标签都抛弃了。上面的这段代码最难懂的也许就是设置开始和结束位置的地方了吧。setStart和setEnd方法都有两个参数,第一个参数是节点,第二个参数有两种意义。如果第一个参数是文本节点,那么第二个参数就是文字的位置。如果不是文本节点,那么第二个参数就是它子节点的位置。用语言描述有点拗口,不够仔细想想应该可以明白。上面代码中的s[2]就是editor的第三个子节点(第一个下标为0),如果你用Chrome的调试工具去看,你会发现,第三个子节点就是第二行的文本节点,后面的参数是0,表示这个文本节点中的第一个字符。后面的s[3]也是类似的,只不过它是指向BR标签,BR里面没有文字和子节点,那么setEnd(s[3],0)就是把结束位置设置到BR本身的位置。其它的代码从注释上就可以理解了,我就不多说了。接着来看看如何获取光标位置在哪个节点的第几个字符。新版的获取代码会比旧版的简单很多,由于有对象,我就不用alert输出了改用conslode.log,你们调试的时候可以按出控制台查看信息。下面是代码
//获取selection对象
var selection=getSelection();
//鼠标事件
document.onmouseup=function(){
  console.log("开始对象",selection.anchorNode);
  console.log("开始位置",selection.anchorOffset);
  console.log("结束对象",selection.focusNode);
  console.log("结束位置",selection.focusOffset);
};
  所有的数据你都可以从selection这个对象中找到,不需要访问range。比起旧标准的操作方法,这个是一个很大的优势。上面这个代码也没什么好说的,开始点的数据保存在anchorNode和anchorOffset中,结束点的位置则保存在focusNode和focusOffset中,只要记住就好了。
  学会上面这么多之后,无论在什么浏览器上对选取的操作都可以很顺利的进行了。如果还有什么疑问可以到本站的提问频道问问神奇的海螺。下面是官方提供的几个文档,有兴趣的话可以看看。

参考文档:
  MSDN TextRange
  MDN Range
  MDN Selection
网名:
54.198.108.*
电子邮箱:
仅用于接收通知
提交 悄悄的告诉你,Ctrl+Enter 可以提交哦
神奇海螺
[查看全部主题]
各类Web技术问题讨论区
发起新主题
本模块采用即时聊天邮件通知的模式
让钛合金F5成为历史吧!
次碳酸钴的技术博客,文章原创,转载请保留原文链接 ^_^