75

简易富文本编辑器实现原理初探

 6 years ago
source link: http://seejs.me/2018/07/29/jy_editor/?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

最近在工作上遇到一个使用自定义编辑器的需求。虽然市面上有很多优秀的开源或非开源的富文本编辑器可供选择,诸如:UEditor、CKEditor,以及随React而生的Draft-js等。但是结合项目中的使用场景——只需要支持文本内容输入、插入表情、插入图片、提到(Mention)功能以及指定代码块即可,通过调研后综合比较得出以下结论:UEditor、CKEditor都比较重量,使用在该场景完全是大材小用,投入产出不成正比;唯一可以基本支持该需求的就只有基于Draft-js进行二次开发,理由如下图:

uMB3meZ.png!web

首先,draft-js足够底层,轻量。其次,有现成的 MentionEmojiImage 插件可以支持大部分业务需求。

但是,通过深度调研后发现Draft-js并不能完全满足我们的需要,例如:

Mention

再加上,本人主观的一点点好奇心——想一探富文本编辑器的实现原理,所以最终还是决定使用原生JS自己开发一个简易富文本编辑器来解决业务问题,下面就跟大家分享一下开发过程中的一些收获。

通常我们在网页里面输入内容通常会使用 <input/><textarea/> 控件,二者的区别在于 <input/> 只支持单行输入,而 <textarea/> 则支持多行输入及内容滚动。而二者的弊端也非常明显:支持纯文本输入,不能给内容增加样式,不能输入自定义表情和图片等富媒体内容。

那么这时候富文本编辑器的诉求就产生了。我们知道,HTML5提供了 contenteditable 属性来控制一个元素是否支持内容修改,其实在此之前html中还有另一个元素可以支持内容可编辑——那就是iframe标签。只要在脚本中执行 iframe.contentWindow.document.body.contentEditable = true ,就可以让该iframe标签可以编辑。但是,随着前端的快速发展,以及HTML5的逐渐普及,使用iframe实现富文本编辑器的方式正在渐渐退出舞台,而由 contenteditable 属性取而代之。

有了前面这些知识,现在要在页面中实现一个编辑器简直太方便了: <div id="J_Editor" contenteditable></div> 这样就可以在这个div标签内编辑内容了。来看一个动画演示:

3Un2eqV.gif

但这远远不够,这离我们的目标太远了。接着我们先对这个基本编辑功能进行一点点升级——增加一个支持现有内容写入编辑器的功能。

既然,编辑器是由一个div元素构成,而我们知道要给一个html元素填充内容,最简单的就是我们可以给这个元素的innerHTML属性赋值: editor.innerHTML = "Hello world!" 。但是,现在我们还有另一种方法来实现相同的功能—— document.execCommand 方法,结合 insertHtmldelete 命令,以及 SelectionRange 对象。来看一个实例:

6RzMVfj.gif

好了,这样一来,一个最原始的编辑器就完成了,但是我们说了,我们还需要支持插入表情和图片。其实不管是自定义表情,还是图片我们都可以统一当做图片来处理,因为在页面中都是通过img标签来体现。那么接下来,我们就来看看如何实现在编辑器中插入图片。其实,和innerHtml类似, document.execCommand 方法还提供了insertImage命令。那么我们就依样画葫芦,直接看演示:

iQfMzim.gif

这样,我们插入图片的功能就实现了。但是这里有个问题——使用insertImage插入的图片没有标示,如果是从Web端发送到客户端上的自定义表情,解析的时候无法区分是表情,还是图片。如图:

AF3yYfU.png!web

因此,在特殊场景下,我们有必要对插入的图片做区分,那么使用insertImage就不能满足我们的需求了,然而,转念一想,其实img标签就是一个html元素,那我们是不是同样可以使用insertHtml命令进行操作呢?

RbqAbu7.gif

这样一来,只要插入表情和插入图片写入img表情的type属性不同,我们就能轻松做出辨别了。至此,编辑器的功能又得到了进一步的强化,还剩下一个拦路虎——Mention功能。

Mention功能,常见于IM业务,用于在群聊中显示“xxx 提到了你”。而我们通常使用过程中,一般都是从编辑器中输入“@”符号开始,选择你要提醒的人。而对于界面交互而言,就是要在编辑器内容发生变化的时候检测光标前面是否是“@”符号,从而触发选择人的逻辑。分析至此我们就可以开始编写代码了:

IzmAJjZ.gif

好了,到这里我们的目标功能好像都完成了。但是,真的就完成了吗?

其实,并没有,真心实践敲代码并测试的码友应该会发现一个比较明显的问题:当出现被@的对象后,先点击其他地方,然后再点击要@的人,此时插入编辑器的内容会默认被插入到编辑器开始的地方,而不是前一次编辑结束的位置。其实,并不止@这里有问题,前面插入内容、图片和表情的地方同样会有这个问题。只要是先让编辑器失去焦点,然后再插入内容,就会被放到编辑器的开头。

显然,这体验并不好,也不符合使用者的习惯,这样的编辑器拿去给用户使用,估计产品经理是会让你见不到明天的太阳的。那么怎么办呢?

我的解决办法是:当编辑器失去焦点的时候,存储当前的光标位置,而当编辑器获得焦点的时候,判断是否有被存储的光标,有则设置到对应位置,否则默认在编辑器开始位置,具体代码如下:

iu6J7fF.gif

至此,基本功能就已经完成了,但是还有很多优化的空间,比如:插入图片的尺寸控制、@应该作为一个整体块处理,但是目前仍可以部分编辑、部分删除,且如果@块有特殊样式,在删掉一半再往后编辑的时候还会影响新增内容的样式等等。

由于篇幅问题,后面的问题如何处理就不再继续扩展了,有时间再通过新的文章进行升级。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK