最近在项目中碰到一个比较棘手的问题:
在iframe富文本编辑器中,有个工具栏,这个工具栏在iframe标签之外,工具栏上有一个按钮,点击该按钮向iframe正在编辑中的光标处插入一个图片,图片会插入到当前光标所在的位置。但由于需求的需要,点击该按钮后需要弹出一个详细选项浮动层,选择详细的类型后再插入,如此,问题来了,当我点击了该按钮,浮动层显示出来后,iframe已经失去焦点,并不知道之前正在编辑的位置,所以编辑器默认把图片插入到编辑器内容的最前边(内部处理),编辑器及浮动层需求如下图:
解决尝试
一、利用模态弹出框
首先声明这种方式是可行的,因为模态对话框会保持iframe编辑器的编辑状态,模态对话框的返回值可直接插入到之前正在编辑的光标位置,就像上面图中其他按钮一样,它们通过点击事件直接插入。但是对于上述需求,只是在该按钮位置添加了一个子类型选择列表框,用模态窗口显然得不到更好的人性化体验,这也不是我们所想要的。
这时候如果能保存之前光标的编辑位置就好了,的确,在按钮的点击事件中,弹出浮动层的同时也保存好光标的位置,然后选择了详细类型后再将光标还原到原来的位置插入图片信息,经过尝试和摸索,令人欣喜的是,这种方式是可行的。
二、保存光标位置,选择后还原(1)
这种方法主要通过document的selection对象来实现,在按钮的点击事件处理程序中,获取当前光标据文档开头的位置(即长度),然后保存,在选择了子类型后,根据之前保存的位置还原光标,然后插入图片信息,代码片段如下:
1 //记录光标的位置,以备后续还原使用 2 var LastPos = 0; 3 //保存当前光标的位置 4 function SaveCusorPos() { 5 //获取编辑器焦点 6 var wobj = document.getElementById("myiframe").contentWindow; 7 wobj.focus(); 8 if (document.selection) { 9 //ie,利用范围进行计算 10 var sText = wobj.document.selection.createRange(); 11 //清除掉当前选中的内容 12 if (sText.htmlText != undefined && sText.htmlText != "") { 13 wobj.document.selection.clear(); 14 } 15 //选择当前光标位置到文档开头之间的内容(以字符为单位) 16 sText.moveStart('character', -wobj.document.body.innerHTML.toString().length); 17 //计算选择内容的长度 18 LastPos = sText.text.length + FliterHtmlTag(sText.htmlText) + 1; //; //sText.htmlText.length; // 19 } 20 else if (wobj.selectionStart || wobj.selectionStart == "0") { 21 //firefox,直接读取编辑位置 22 LastPos = wobj.selectionStart; 23 } 24 }
上述代码不难理解,在ie中需要用范围计算当前光标位置距离文档开头的距离,而在firefox中,直接可以用编辑对象获取当前的编辑位置,下面是光标还原的代码:
1 //把光标还原到之前保存的位置 2 function SetCusorPos() { 3 //获取编辑器对象焦点 4 var wobj = document.getElementById("myiframe").contentWindow; 5 wobj.focus(); 6 if (wobj.document.body.setSelectionRange) { 7 //firefox,直接通过函数定位光标 8 wobj.document.body.setSelectionRange(LastPos, LastPos); 9 } 10 else if (wobj.document.selection.createRange()) { 11 //ie,用selection对象进行选择 12 var range = wobj.document.selection.createRange(); 13 range.collapse(true); 14 //将选择区域的开始位置和结束位置都移动到之前保存的点 15 range.moveEnd('character', LastPos); 16 range.moveStart('character', LastPos); 17 //定位光标的位置 18 range.select(); 19 } 20 }
在不同的浏览器中,处理方式均不一样,不过有一点是相通的,它们都是通过将选取的开始位置和结束位置重合来定位光标。
经测试,这种方式是可行的,但它只能在纯文本处理的时候有用(IE中),问题在于这个保存点的计算,通过选区Text的length获取的长度是只是这个选区的文字长度,它并不能过滤多媒体元素(如图片、音视频等),这些元素在这个length中并没有包括,故存在多媒体元素的时候,这个光标保存点是不准的,会在实际位置的前面插入。此外,选区还有另外一个属性htmlText,获取它的长度又如何呢!?答案也是不行,这个长度包含了选区中html标签的所有字符,比如换行,段落等都被计算在内,这个光标保存点比实际的要大的多,会在实际位置的后面插入。
二、保存光标位置,选择后还原(2)
上述两种方法都有自己的缺陷,经过摸索和查阅相关资料,在IE中有第三种方法可以实现此功能,就是selection对象的getBookmark和moveToBookmark两个方法,前者获取一个对象,这个对象记录了当前编辑器中光标的位置信息,后者根据这个位置信息还原光标的位置。代码如下:
1 //存储之前光标位置信息的对象 2 var ieSelectionBookMark = null; 3 //保存当前光标的位置 4 function SaveCusorPos() { 5 //编辑器获取焦点 6 var wobj = document.getElementById("myiframe").contentWindow; 7 wobj.focus(); 8 if (document.selection) { 9 //获取当前光标的位置 10 var rangeObj = wobj.document.selection.createRange(); 11 ieSelectionBookMark = rangeObj.getBookmark(); 12 } 13 } 14 //把光标还原到之前保存的位置 15 function SetCusorPos() { 16 //编辑器获取焦点 17 var wobj = document.getElementById("myiframe").contentWindow; 18 wobj.focus(); 19 if (ieSelectionBookMark) { 20 //还原光标的位置 21 var rangeObj = wobj.document.selection.createRange(); 22 rangeObj.moveToBookmark(ieSelectionBookMark); 23 rangeObj.select(); 24 ieSelectionBookMark = null; 25 } 26 }
上述代码改写了第二种方法中的两个函数,比较简洁,但这种方式在IE8中测试通过,其他不同版本浏览器中有待进一步验证,其他浏览器如firefox,利用第二种方式就可以实现。
如今浏览器五法八门,各自对标准的支持也不一样,导致了前端开发者做了大量的工作来弥补兼容性,不管怎样,相信会越来越好~~~