前言
近期研究了富文本编辑器相关内容,在网上找了很多相关的资料进行学习。发现富文本编辑器相关的资料比较零碎,无法形成系统性知识,这里通过系统性的专题记录下了解到的关于富文本相关知识。
富文本编辑器发展历程
开源的富文本编辑器技术通常长期的发展,可以分为三个阶段:
- L0
编辑器技术的起始阶段,依赖于浏览器原生编辑能力。通过浏览器execCommand方法,实现对编辑内容的加粗、斜体、回车...。execCommand是浏览器的原生api,允许通过命令操纵可编辑区域(contenteditable)的元素。还不了解execCommand的同学可以去MND document.execCommand 看看。
- L1
基于浏览器contentditable富文本的编辑能力,自定义实现编辑区域操作命令。在浏览器基础能力和排版能力上,满足大部分编辑器自定义需求。L1能力的编辑器代表如下:
- Quill.js 基于浏览器 contenteditable 编辑能力实现的富文本编辑器,石墨文档就是基于Quill实现的
- ProseMirror也是依赖于浏览器contenteditable能力,引入了Schema、插件系统、Virtual DOM等等的富文本编辑器,国外的产品Confluence 就是基于ProseMirror,由此可见ProseMirror的稳定性和扩展性强大。近期研究富文本编辑器也是主要看ProseMirror相关内容,后面其他文章会基于ProseMirror原理来分享编辑器相关知识。
- Draft.js,Facebook团队推出的把富文本编辑器和React结合的富文本作品。在编辑器中引入了状态管理、Immutable等等React的概念。
- Slate.js相对于其他编辑器框架,最晚推出。站在巨人的肩膀上,借鉴吸收了ProseMirror、Quill、Draft的优点。Slate.js只提供了编辑器的基础能力,定制化极强。很多富文本编辑器团队都会给予Slate做二次开发,定制自己团队的编辑器。钉钉文档就是基于Slate.js开发的。
- L2
不依赖浏览器的编辑能力,自己实现选区、光标、排版。目前只有Google Doc实现了L2能力的富文本编辑器,开发难度以及成本很大。
基于execCommand实现简单的L0富文本编辑器
通过执行document.execCommand,操纵可编辑器区域内容的元素。 document.execCommand还支持很多文本编辑能力,感兴趣的同学可以自己查阅文档进行添加。 代码如下:
js
// Editor.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
* {
margin: 0;
padding: 0;
}
#menu-bar {
margin: 100px 60px 10px;
display: flex;
}
.btn {
padding: 4px;
margin-right: 8px;
border-radius: 2px;
border: 1px solid #ccc;
background: #82c8a0;
color: #fff;
}
#editor {
height: 600px;
padding: 10px 4px;
margin: 0px 60px;
outline: none;
border: 1px solid #ccc;
}
</style>
</head>
<body>
<div id="menu-bar">
<button id="bold" class="btn">加粗</button>
<button id="italic" class="btn">斜体</button>
<button id="underline" class="btn">下划线</button>
<button id="justifyCenter" class="btn">居中</button>
<button id="justifyLeft" class="btn">左对齐</button>
<button id="justifyRight" class="btn">右对齐</button>
<button id="insertOrderedList" class="btn">有序列表</button>
<button id="insertUnorderedList" class="btn">无序列表</button>
<button id="h1" class="btn">H1</button>
<button id="h2" class="btn">H2</button>
<button id="" class="btn">...</button>
</div>
<div id="editor" contenteditable="true"></div>
</body>
<script src="./editor.js"></script>
</html>
js
// Editor.js
function bold() {
//加粗
return document.execCommand('bold', false, null);
}
function italic() {
// 斜体
return document.execCommand('italic', false, null);
}
function underline() {
// 下划线
return document.execCommand('underline', false, null);
}
function justifyCenter() {
// 居中
return document.execCommand('justifyCenter', false, null);
}
function justifyLeft() {
// 左对齐
return document.execCommand('justifyLeft', false, null);
}
function justifyRight() {
// 右对齐
return document.execCommand('justifyRight', false, null);
}
function h1() {
// 1级标题
return document.execCommand('fontSize', false, 7);
}
function h2() {
// 2级标题
return document.execCommand('fontSize', false, 6);
}
const menuButtons = {
'bold': bold,
'italic': italic,
'underline': underline,
'justifyCenter': justifyCenter,
'justifyLeft': justifyLeft,
'justifyRight': justifyRight,
'h1': h1,
'h2': h2
//...
};
function setupEditor() {
const menuBars = document.getElementById('menu-bar').children;
for(let bar of menuBars) {
const id = bar.getAttribute('id');
bar.addEventListener('click', menuButtons[id]);
}
}
setupEditor();
L0编辑器痛点
上面通过浏览器contentEditable + document.execCommand实现了一个简单的富文本编辑器,这种编辑器存在很多坑。不用浏览器厂商对contentEditable实现方案不同,导致contentEditable中生成的Dom结构不可控。 例如通过Enter回车创建一个新的文本行,预期是换行。但是在不同浏览器中新的一行html标签各不相同。Chrome/Safari以及Firefox60+中是div标签,IE中是p标签。还有很多这种类似问题,这里就不展开说明啦。