2019-05-27 | JavaScript | UNLOCK

基于1行核心JS代码实现简单的富文本编辑器

之前的一个博客项目中,有使用富文本编辑器的需求,考虑到已有很多功能完备且强大的编辑器,从零开始开发新的编辑器没有必要,且在有限时间内完成质量无法保证,便直接选用了TinyMCE供项目使用,类似的还有Quill Rich Text Editor等。

近期的学习过程中又遇到类似的问题,于是整理了手头的资料,写了一个Demo上传至Github,可以在STEditor体验效果,具体详情可前往我的Github仓库查看。

原理

当一个HTML文档切换到设计模式时,document暴露execCommand方法,该方法允许运行命令来操纵可编辑内容区域的元素。

大多数命令影响documentselection(粗体,斜体等),而其他命令插入新元素(添加链接)或影响整行(缩进)。当使用contentEditable时,调用execCommand()将影响当前活动的可编辑元素。

以上描述引用自MDN文档中关于document.execCommand的介绍,从中可以看出,要实现基本的编辑器功能,只需要使用contentEditable属性设置文本区域可编辑,再配合document.execCommand方法操纵可编辑区域元素即可。

语法

1
bool = document.execCommand(aCommandName, aShowDefaultUI, aValueArgument)

返回值为Boolean,如果是false表示操作不被支持或未被启用。需要注意的是,该命令只有在实际调用时生效,如果直接在浏览器中输入,会返回false,不能以返回值校验浏览器的兼容性。具体参数如下:

aCommandName

一个DOMString,为命令名称,必选项,可用命令列表可参考MDN给出的命令

aShowDefaultUI

一个Boolean,是否展示用户界面,一般为false

aValueArgument

可选项,一些命令需要额外的参数,如插入链接时需要提供链接对应的url,默认为null。

实现

我们要实现的编辑器大体分为两个部分,顶部用于格式控制的控制区域,和底部用于输入和显示的文本区域。可以先在HTML文档中写出基本结构。

1
2
3
4
<div id="container">
<div id="menubar"></div>
<div id="content" contenteditable="true"></div>
</div>

因为要实现文本区域可编辑,所以要在文本区域设置contenteditable属性为true

控制区域

接下来的工作主要分为两块,控制区域和文本区域,和基本HTML结构对应。控制区域主要为各种格式化按钮的绘制,基本结构可以采用<a>标签,也可以使用<button>标签,这里我使用的是后者,同时结合Font Awesome显示图标。

1
2
3
4
5
6
7
8
9
<!-- 调整文本为粗体 -->
<button type="button" data-command="bold" title="Bold">
<span><i class="fa fa-bold fa-lg" aria-hidden="true"></i></span>
</button>

<!-- 设置文本为1级标题 -->
<button type="button" data-command="formatBlock" data-value="H1" title="Heading1">
<span>Heading 1</span>
</button>

上面的两段代码分别对应加粗和添加块式标签两个命令,区别在于前者仅需要传递命令名称,后者需要传递一个标签名称字符串参数以标识创建哪种块元素。

这里我们采用data-*这个HTML5新增的自定义Attribute,详情可参考这里data-command标识命令名称,data-value标识命令需要的额外参数,如果有的话。

文本区域

HTML页面中完成data-*的设置后,接下来要做的是当点击控制区域触发click事件时,获取事件源传过来的参数,进行处理后响应到文本区域中选中的文本上。我们只考虑如何传递参数,调用命令,也就是上面提到的document.execCommand,暂不关心浏览器如何执行对应命令。

1
2
3
4
5
function changeStyle(data) {
//调用document.execCommand方法进行编辑
data.value ? document.execCommand(data.command, false, data.value) :
document.execCommand(data.command, false, null);
}

核心的处理函数如上,这里我们在执行命令前做一个简单判断,看是否有除命令名称外的额外参数。在JS文件中,我们可以通过如下方式读写data-*的值:

1
2
3
4
5
6
//获取加粗按钮
var btn = document.querySelector("#bold-btn");
//获取加粗按钮对应的command
var command = btn.getAttribute("data-command");
//设置加粗按钮对应的命令为斜体
btn.setAttribute("data-command", "italic");

也可以通过HTML5原生的APIdataset来读写data-*

1
2
3
4
5
6
//获取加粗按钮
var btn = document.querySelector("#bold-btn");
//获取加粗按钮对应的command
var command = btn.dataset.command;
//设置加粗按钮对应的命令为斜体
btn.dataset.command = "italic";

如果你使用jQuery,还可以使用下面的方式:

1
2
3
4
5
6
//获取加粗按钮
var btn = $("#bold-btn");
//获取加粗按钮对应的command
var command = btn.attr("data-command");
//设置加粗按钮对应的命令为斜体
btn.attr("data-command", "italic");

这里我使用HTML5原生的dataset获取参数,在button对应的事件处理函数中调用changeStyle方法实现文本编辑。

到这里基本的功能已经实现,剩下的工作就需要靠CSS完成了。下面是该Demo的预览:

小结

实际上,本文中的富文本编辑器仅是最简单的一种实现,所有的命令调用都通过document.execCommand这一条命令,容易出现覆盖现象,且没有重写样式,所有的文字样式均由浏览器默认指定,不同浏览器显示效果也有差异,存在问题显而易见。

现有的功能完善且强大的编辑器多有自己的实现,从样式重写到事件监听,不过分依赖于原生的接口,可实现功能更丰富,也更稳定。总体表现更出色,实现起来也要复杂的多,不在本文要讨论的范围内。

评论加载中