你所不晓得的富文本

1,238 阅读3分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

1、前言

话说前端几大“浪漫”,图形学,复杂表单 Formily, 富文本 语雀,前赴后继迎来送往多少人进进出出,各种尝试改进和优化,但是依然不能面面俱全的满足pm的“奇思妙想”,比如说我们今天面临的需求是,富文本中插入下拉框。

image.png

2、简图

image.png

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

image.png

Trends

image.png

3.2 Quill

quill代码结构清晰,是一款偏底层,具备良好的兼容性及强大可扩展性的富文本编辑器。其中最大亮点是DeltaParchmentDelta是quill 对html文档高度抽象出来的数据层,这层可以描述任何quill文档,Parchment 是quill对dom的抽象类似虚拟dom。数据更新视图模型: Delta—Parchment—DOM;视图更新数据模型:DOM—MutationObserver—Delta;

4、实现

quill 插入非文本内容,是通过 扩展blot来实现,内置了常用的img 和 video,其他形式的内容(table,游戏,文件等)需自己通过继承BlockEmbed 来实现。例如我们今天来实现插入一个Select的多级联选。首先将大象装入冰箱需要几步:

  1. 扩展toolbar,增加插入icon。
  2. 自定义class实现插入内容(Blot).
  3. 注册第二步的Blot.
  4. 通过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 富文本有很多好的设计理念,值得深入学习,后续有时间会分享下源码篇。

参考链接:
1、技术选型多维度考虑
2、npm包趋势