定义:
富文本编辑器(Rich-Text Editor)简称RTE,是一种可内嵌浏览器,所见即所得的文本编辑器
编辑器实现原理:
大概分为3种:
- 在textarea上定位各种样式。这是 Facebook 早期评论系统所使用的。
加粗斜体等操作都难支持, 已经弃用 - 实现自己的布局引擎,连闪烁的光标都是通过div控制的。这是 Google Docs 所使用的。
工作量非常巨大, 只有谷歌微软这样的能自己造浏览器的巨头才玩得好 - 使用浏览器原生的ContentEditable编辑模式。这是绝大多数现有富文本编辑器所使用的。
打开控制台,在body上加入contenteditable属性,就会变成编辑器,快捷键,撤销栈,光标,输入法, 兼容性浏览器都替你处理好了
开源编辑器:
现在有很多开源编辑器,这里就不一一介绍了,你可以去网上搜索,随便举几个例子
- 百度UEditor
- ProseMirror
- slate
- quill
- EtherPad
- Draft
产品:
-
飞书 (字节跳动旗下办公产品)
- 技术:Etherpad
-
语雀 (阿里旗下)
- 技术:一开始用的slate,后来自己开发的
- 因为slate有个秒崩的缺陷,拼音输入就崩,所以有bug的不建议使用
-
金山文档WPS(金山办公软件):
- 光标都是自己开发的
-
有道笔记(网易):
- 一代2012年:当时安卓自带的webview不支持contenteditable
- 二代:contenteditable特性。但是不同浏览器有差异
- 三代2015:不再依赖浏览器的contenteditable特性
-
石墨文档(react)
-
腾讯文档
调研了3个富文本
一、Quill(个人推荐)
旨在兼容性和可扩展性github
quill 官网是有一些基本功能的,他的核心是blot和parchment,就是DOM树的副本。数据存储方面用的是delta(一个json格式的) 所以你可以在quill的基础上扩展你想要的功能,比如你要自定义个他没有的功能,比如自定义一个分割器大概分为一下几个步骤
- 继承parchment的blot 的 BlockEmbed
- 定义一个分割器的 blotName 和tagName
- 注册到Quill(不注册是Quill是不认识他的)
- 自定义个button
- 找到光标所在位置,插入你要插入的内容,并且把光标后移一下
import Quill from 'quill'
let BlockEmbed = Quill.import('blots/block/embed')
class Divider extends BlockEmbed { }
Divider.blotName = 'divider'
Divider.tagName = 'hr'
Quill.register({
'formats/divider': Divider
})
<!--hrml-->
<button class="custom-hr" @click="insertDivider()"></button>
<!--js-->
insertDivider() { // 自定义的分割线
let range = this.quills.getSelection(true); //获取光标位置
if (range != null) {
this.quills.insertText(range.index, '\n', Quill.sources.USER); // 插入回车符
this.quills.insertEmbed(range.index + 1, 'divider', true, Quill.sources.USER); //在光标+1的位置插入一个<hr>标签
this.quills.setSelection(range.index + 2, Quill.sources.SILENT); // 将光标往后移一个
}
},
缺点:
不支持表格,支持表格的是dev2.0版本,但是未发布,虽然可以投入到生成中,但是最大的问题是如果你使用了dev2.0版本,很多插件比如@提及功能,多选框等功能 不支持在2.0中使用的
兼容性:
数据存储格式Delta:
- Quill.getContents():
{"ops":[{"insert": "\n", "attributes": {"background": "red"}}]} - Qull.setContent(delta) : 文档的回显
二、ProseMirror(特点:灵活,配置项强)
1.定义:
prosemirror不是一个大而全的框架,它是由无数个小的模块组成,它就像乐高一样是一个堆叠出来的编辑器。
2. 四个核心库(就像4个轮子)
- prosemirror-model:定义编辑器的文档模型,用来
描述编辑器内容的数据结构 - prosemirror-state:提供
描述编辑器整个状态的数据结构,包括selection(选择),以及从一个状态到下一个状态的transaction(事务) - prosemirror-view:实现一个在浏览器中将给定编辑器状态
显示为可编辑元素,并且处理用户交互的用户界面组件 - prosemirror-transform:包括以记录和重放的方式修改文档的功能,这是state模块中transaction(事务)的基础,并且
它使得撤销和协作编辑成为可能。
3. 数据存储
import {EditorState} from "prosemirror-state"
解析器:DOMparse 或者MaekdownParser 把dom渲染成一个Node对象。
序列器: 调用EditorState.JSON()可以把当前状态doc序列成json格式
4. 缺点
- 学习曲线稍微陡峭:文档都是英文,中文资料少,有问题就去github上找
三、 slate框架
定义:
所有逻辑通过一系列的插件实现的。灵感来源如Draft.js,ProseMirror,Quill等类库。你可以将它理解为在React 和Immutable基础上,一种可插拔的 contenteditable 实现.目前还在beta测试状态,社区驱动
支持程度:移动端:支持ios,但是没有定期测试。 0.47 支持安卓on 谷歌,但是0.50+ 当前不支持, 支持ios pc端最新的谷歌,Edge,火狐,safari 进行了测试,在IE中不行
- 开发语言:JAVA
- 授权协议:MIT
- 数据存储格式: JSON
- 操作系统:跨平台
- 对等依赖:React
- 数据结构:immutable (ProseMirror的)
- 版本:0.1.0 - 0.57.0 (2019年12月18号)
- 秒崩bug:知乎2019-12-26
- 传说的表格和协同编辑只是理论上支持,需要大牛搞
immutable 是FaceBook开发的不可变数据集合。不可变数据一旦创建就不能被修改,使得应用开发更简单,允许使用函数式编程技术,比如惰性评估。允许高效的队列方法链,类似 map 和 filter ,不用创建中间代表。 immutable 通过惰性队列和哈希映射提供 Sequence, Range, Repeat, Map, OrderedMap, Set 和一个稀疏 Vector。
优点:
- 支持所有浏览器
- api文档很好,发展迅速,社区活跃,作者修改还比较积极
- 链式操作:实现协同编辑
- 使用immutable(不可修改,js对象属性是可以修改的)
不存在引用问题, 子节点随意的改动都会生成一颗新树,可以轻松实现富文本的撤销和重做操作。并且支持完全复杂的嵌套开表达文档的树形结构。
在slate 和Draftzhong ,富文本数据就是对immutable的一层封装, 从而自带了对
撤销操作的支持不需要额外编码实现,但是Slate 比Draft多了支持嵌套的数据结构
编辑操作时发生的处理流程:
- 用户在编辑器光标所在的 Node 内按键,触发事件。
- 根据按键的键值,分发不同的 Change,如换行、加粗等。
- Change 修改 State,生成新 State。
- 新 State 经过 Schema 校验后,渲染到编辑器内,按需更新相应的 Node。
整个流程中最核心的机制可概括为一个公式:state.change().change(),Change 是一个非常优雅的 API,所有的变换都是都通过 Change 对象实现的。
对比:
| 对比 | slate | ProseMirror | Quill |
|---|---|---|---|
| watch | 295 | 130 | 479 |
| start | 16.2k | 4.2k | 25k |
| fork | 1.6k | 230 | 2k |
| 数据存储 | JSON | JSON | JSON输入输出 |
| 支持程度 | 0.5+不支持安卓 | 兼容性很好基本都支持 | |
| 特点 | 依赖React,immutable | 四个核心库,配置灵活 | 开箱即用 |
| 推荐指数 | 不推荐,输入法有bug | 推荐 | 推荐 |
quill开发H5缺陷:
- h5限制:光标会和键盘一起出现
点击加粗格式,聚焦文本时都会引起键盘的弹起,如果你的格式设置放在页面的底部,就会被键盘挡住, 并且键盘的时而弹起也会非常影响体验
- 安卓 ios 兼容性
安卓不支持选中