前言
本例基于react,但是实际上就是用原生js做的。兼容性做到了IE9,但是按照这个思路做是可以做到IE8甚至更低的。
需求与最初的思路
当我拿到这个需求的时候以为很简单,就是可以给页面上的文章做记号,比如添加个下划线,或者背景涂色做成荧光笔的样子。
因为只需要兼容IE9,所以window.getSelection是支持的。(IE8及以下有其它的获取选中的方法)
那么思路就是选中文本,点击添加下划线后,通过 window.getSelection.getRangeAt(0) 拿到选中的文本对象,获取到文本后,通过文本对象的 surroundContents 方法来将文本替换为带有class的元素。
初步的实现
思路很简单,代码同样也很简单。
CSS代码:
.custom-underline{ border-bottom: 1px solid #f00; font-style: normal; } .nite-writer-pen{ background-color: lightgreen; border-radius: 5px; box-shadow: 0 0 10px lightgreen; font-style: normal; }JS代码:
/** * 用元素替换被选中的文本 */ var replaceSelectedStrByEle = function(className){ var selecter = window.getSelection(); var selectStr = selecter.toString(); if (selectStr.trim != "") { var rang = selecter.getRangeAt(0); var ele = document.createElement("i"); ele.className = className; ele.textContent = selectStr rang.surroundContents(ele); } } replaceSelectedStrByEle('nite-writer-pen');天坑出现
上面的思路实在是过于简单,如果是一个很简单的元素,那么这种做法是没有问题的。
但是我们的文章的html结构一般都没有这么简单,比如对于以下情况:
<p> <p>道可道,非常道。</p> <p>名可名,非常名</p> </p>如果在页面上我选中的操作如下:

那么上面的代码实现就会出现BUG,对于这种跨元素选中的情况,想当然的用元素去替换文本是没用的。
如果你想得更多,比如跨多个元素选中,以及选中元素为更为复杂的html结构,你就会发现这是一个多大的坑。
html结构有多复杂,这个坑就有多深。
思路的僵局与写轮眼
其实天坑也不是完全没有路走,在跨多个元素选中的过程中,我想给选中的内容加样式,那么就需要获取到所有选中的文本节点,并且批量替换成元素。
但是 window.getSelection.getRangeAt(0) 获取到的range对象只能获取到最开始选中的节点和最后选中的节点的。
那么接下来通过选中的最开始的节点和最后的节点获取到所有的文本节点。
思路就是这么个思路,但是实现起来是很复杂的。
面临深坑,肯定不可能硬刚。
毕竟我已经不是当年头铁的愣头青了,做项目是有时间和精力成本的,如果要填掉这个坑,那么加班是不可避免的,最重要的是在有限时间内填的这个坑可能还有各种BUG和兼容性问题。
对待这种天坑,一般就给需求来个做不了三连了。
但是这把我想赢,毕竟这个东西看起来确实简单。
在外人的眼里,如此之简单,分分钟搞定的事情。这都做不了,我还怎么在前端的圈子里继续划水?
所以我要动用程序员的入门技——写轮眼。

然而百度、谷歌无效,根本没有这个解决方案,有的全是些我最初的简单实现。

但是回忆我们以前见到的各种网页应用与场景,很容易就能想到上面的这种操作我们是见过的。
那就是从远古IE时代就已经出现的各种富文本编辑器组件。
目标确认,百度的ueditor,这波我要赢。

分析ueditor与复制
上github两三下拿到ueditor源码,开始读源码分析代码。
中间过程不再多说,精简代码,除去一些不需要的代码和兼容性处理后,拿到了五个文件:
- browser.js (浏览器版本判断,用于做兼容性处理)
- domUtils.js (dom操作)
- dtd.js (节点的类型与元素判断)
- Range.js (封装的选中范围对象)
- utils.js (工具类)
即使精简后,代码也不少,大概两三千行。不过其中还有很多注释,压缩后体积并不大。
由于代码比较多,这里就不全部展示了,文章最后会给出github的地址。
这里只给出最后的使用代码:
