本文已参与「新人创作礼」活动,一起开启掘金创作之路。
1、前言
话说前端几大“浪漫”,图形学,复杂表单 Formily, 富文本 语雀,前赴后继迎来送往多少人进进出出,各种尝试改进和优化,但是依然不能面面俱全的满足pm的“奇思妙想”,比如说我们今天面临的需求是,富文本中插入下拉框。
2、简图
3、分析
富文本是一种典型的所见即所得(what u see is what you get)的使用场景,通常实现方式有
三种,一种是古老的<textarea>,早期被Facebook用在评功能能,但是连基本的加粗和斜体都很难实现,后续直接被放弃,第二种是通过iframe来实现,将整个空白html文档变成可编辑的,其核心是控制designMode属性开关(on/off) 例如contentWindow.document.designMode = "on";最后一种是大名鼎鼎的contenteditable属性,非常灵活的可以给大多数Element元素增加后使其变为可编辑状态。
3.1 富文本哪家强?
都是富文本,你该怎么选?看看主流的比对,结合自己业务选一个。怎么选,👇参考下面外链。
Q:直接点给个参考,怎么选? -- how
A:看有没有数据抽象层。 -- what
Q:给点提示
A:有数据抽象层意味着UI和数据分离,便于二次开发(需求定制)。 -- why 选了几个主流的横向对比分析:
Stats
Trends
3.2 Quill
quill代码结构清晰,是一款偏底层,具备良好的兼容性及强大可扩展性的富文本编辑器。其中最大亮点是Delta和Parchment。Delta是quill 对html文档高度抽象出来的数据层,这层可以描述任何quill文档,Parchment 是quill对dom的抽象类似虚拟dom。数据更新视图模型: Delta—Parchment—DOM;视图更新数据模型:DOM—MutationObserver—Delta;
4、实现
quill 插入非文本内容,是通过 扩展blot来实现,内置了常用的img 和 video,其他形式的内容(table,游戏,文件等)需自己通过继承BlockEmbed 来实现。例如我们今天来实现插入一个Select的多级联选。首先将大象装入冰箱需要几步:
- 扩展toolbar,增加插入icon。
- 自定义class实现插入内容(
Blot). - 注册第二步的
Blot. - 通过
insertEmbed插入自定义的内容 。
4.1 扩展toolbar,增加插入icon
toolbar 本身是module的一种形式,其支持两种方式扩展:
- 通过配置json数据:
// 自定义配置
const toolBarConfig = [
[{ header: ['1', '2', '3', false] }],
['bold', 'italic', 'underline', 'link'],
[{ list: 'ordered' }, { list: 'bullet' }],
['card', 'divider', 'emoji', 'file', 'tag'],
['cusotm-select'], // 我们扩展的
];
// 增加 icon
const cusotmSelectIcon = `<svg>...</svg>`;
const icons = Quill.import('ui/icons');
icons.cusotmSelect = cusotmSelectIcon;
// 实例化
const quill = new Quill('#editor', {
theme: 'snow',
modules: {
toolbar: {
container: toolBarConfig,
handlers: {
... // 增加 自定义 handlers事件
cusotmSelect(value) { console.log('cust handle~~'); },
},
}
},
});
- 通过给定html模版容器:
<div id="toolbar">
<!-- Add buttons as you would before -->
<button class="ql-bold"></button>
<button class="ql-italic"></button>
<!-- But you can also add your own -->
<button id="custom-button"></button>
</div>
<div id="editor"></div>
// js
var quill = new Quill('#editor', { modules: { toolbar: '#toolbar' } });
4.2 自定义class实现插入内容(Blot)
核心就是定一个class, 通过js来实现展示内容和逻辑,quill 不关心里边的内容。cusotmSelect 接受一个父节点用来挂载要实现的内容,另外接受一个数据,数据格式参考Cascader 级联选择器的options。
import Quill from 'quill';
const BlockEmbed = Quill.import('blots/block/embed');
class cusotmSelectBlot extends BlockEmbed {
static blotName = 'cusotmSelect';
static tagName = 'div';
static create(value){
const node = super.create(value);
new cusotmSelect(node, data); // 见👇
return node;
}
}
export default cusotmSelectBlot;
class cusotmSelect {
// 大体思路通过遍历data 创建ul 和li 并添加点击事件,实现起来比较简单,代码比较长就不贴了。
constructor(parentNode, data) {
this.value = data;
this.init();
...
parentNode.appendChild(node);
}
}
4.3 注册cusotmSelect Blot.
import cusotmSelectBlot from './cusotmSelectBlot';
Quill.register('formats/cusotmSelectBlot', cusotmSelectBlot);
4.4 通过insertEmbed 插入自定义的内容.
const quill = new Quill('#editor', {
theme: 'snow',
modules: {
toolbar: {
container: toolBarConfig,
handlers: {
...
cusotmSelect(value) {
const index = this.quill.getSelection().index;
// 插入自定义内容
this.quill.insertEmbed(index, 'cusotmSelect', {
...
参数对应 create()方法中的参数
});
},
},
}
},
});
5、总结
quilljs 富文本有很多好的设计理念,值得深入学习,后续有时间会分享下源码篇。