做过前端的朋友,大家或多或少都接到过做用户交互的需求。这时候只要一听到产品经理说能不能支持加粗,斜体和@好友,大家肯定就会在心里默念“S**t,又要跳富文本编辑器的坑了”。
曾经知乎上面有过一个问题,有多大比例的前端工程师,能在合理的时间内独立开发出一个足以供商业网站使用的文本编辑器?大家一致认为在千分之一,甚至万分之一左右。这样我们要先了解富文本编辑器的各种特性(坑),才能往成为千分之一的道路前进。
富文本编辑器的坑在我总结起来有两大类:
一是视图层的绘制,Facebook早期是使用<div>
在<textarea>
上面实现效果,并且需要大量使用了DOM测量。早期Facebook工程师发愁的事情之一就是提及的蓝色背景与文字偏离。
二是各种编辑时候的各种小(天)坑。比如光标位置,输入法,兼容性。。。
面对上面的各种坑,一个传统的解决办法是使用DOM的特性,contentEditable。不过说到这很多同学要开始翻白眼了,虽然有很多不错的实现(tinyMec,Quill),但是contentEditable还是有名的难易控制,并且在不同版本实现不同。
不过在React出现之后,事情有了转机。作为一个视图层库,React能够很好的抽象原生DOM的操作,让开发者可以使用组件来定义不同的富文本格式。可以说一次性的降低了富文本编辑器的开发难度。
在React.js Conf 2016上面,Facebook开源了Draft.js,作为Facebook一众文本编辑需求的产品上面的新选择(状态发布,评论,Facebook Note,Messenger)。
Draft.js除了与React紧密的整合之外,另外一个很大的优势就是利用contentEditable这个DOM特性解决一众编辑器小坑的同时,使用immutable的数据结构来代表编辑器的状态,很好的分离了DOM和state。所以除了Facebook,很多公司也上手了Draft.js,比如知乎。
Draft.js给富文本编辑器提供了一个很简单明了的解决办法,就是React -> view,immutable.js -> model,然后Draft.js提供编辑器操作作为controller。让我们来看下面一个例子:
import React from 'react';
import ReactDOM from 'react-dom';
import {Editor, EditorState} from 'draft-js';
class MyEditor extends React.Component {
constructor(props) {
super(props);
this.state = {editorState: EditorState.createEmpty()};
this.onChange = (editorState) => this.setState({editorState});
}
render() {
return (
<Editor editorState={this.state.editorState} onChange={this.onChange} />
);
}
}
ReactDOM.render(
<MyEditor />,
document.getElementById('container')
);
editorState可以理解为编辑器在某一刻的一个快照(snapshot)。同时editorState也是一个由不可变(immutable)数据类型构成的对象。依据editorState,我们可以实现对编辑器内容的严格控制,并且知道如何render编辑器界面。
而富文本功能的实现,既可以通过已有的html element(h1,h2,ul,ol)也可以通过自定义的CSS。
.superFancyBlockquote {
color: #999;
font-family: 'Hoefler Text', Georgia, serif;
font-style: italic;
text-align: center;
}
编辑器的操作则是非常的函数式化,之前我提到过,每个editorState可以看做编辑器的一个快照。这样每个操作(加粗,删除等)输入参数为editorState,返回结果也是一个editorState,再由DOM去render。这样状态机的设计,既函数式化,又可以保证将编辑器操作流水线化。比如切换code模块的接口就是:
toggleCode: (editorState: EditorState) => EditorState
除了Draft.js,现在开源社区还有一个类似的很火的开源富文本编辑库 - Slate。
Slate除了沿用很多Draft.js的先进的想法之外,更是在插件化,简化API接口方面做出了自己的特色。现在Gitbook和语雀都使用了Slate作为底层编辑器,而且前几天在Hacker News上面出现以后更是star数目一跃到了12000+(迫近Draft.js)。Slate官方介绍自己的优势有:
- 插件是一等公民
- 文档格式不设限
- 层级文档模型
- 与DOM平行
- 无状态的视图层与不可变的数据层
- 直观的操作
- 可协调的数据模型
- 明确的核心逻辑
之前在旧金山跟Slate创始人Ian面基的时候,他也分享过自己开发富文本编辑器遇到过得各种坑和各种思考,并且希望把Slate做成一个完全插件化的编辑器框架。(p.s.,不过作为个人开发者,Ian小哥的任性,比如对IME和协同的支持的抉择过程,也非常有意思)。
Slate的代码模块化程度非常高,设计也力求插件化优先。小哥作为Rhode Island School of Design毕业,设计师转的程序员,对接口设计的追求可见一斑(没事改API接口名称的习惯就不吐槽了)。
如果大家感兴趣,我也计划陆续推出几篇解读这个框架源码的文章。
------各单位注意,马上要开始打广告了:p------
在研究了这么多富文本编辑器,WYSIWYG编辑器之后,我和我的好基友也开发了一款面向hacker的笔记应用 - Tea:
现在已经开始内测啦!主打的特色有:
- 插件是一等公民,比如,这个插件:
- 极简的界面,
- 实时渲染的 Markdown,
- 各种与编辑器相通的快捷键,
- 代码高亮等功能
对我们感兴趣的同学可以去看我们在掘金上的专栏:https://juejin.im/post/5bffadf3f265da616a476096,里面有我们项目更详细的介绍和内测版下载链接。