Quill 自定義富文本功能

前言

富文本是給輸入的內容增長樣式,Quill 提供 api 的方式去判斷修改 DOM ,而不像不少其餘的編輯器經過遍歷 DOM 樹的方式來作到這點。而且 Quill 支持定製化功能,接下來來了解下如何使用 Quill 及它的相關源碼css

quickstart

如同 Vue,React 同樣,Quill 也須要已存在的 dom 元素做爲 Quill 實例掛載的目標node

基礎配置

參數一:能夠是dom,能夠選擇器(自動去轉換成對於的dom)。 參數二:配置對象,關於富文本的一些配置選項。npm

var editor = new Quill('#editor', options);
複製代碼

options 配置對象json

var options = {
  modules: {
    toolbar: '#toolbar'
  },
  placeholder: 'Compose an epic...',
  theme: 'snow'
};
複製代碼

modules.toolbar

用於配置工具欄
方式一:css標籤api

var quill = new Quill('#editor', {
  modules: {
    // Equivalent to { toolbar: { container: '#toolbar' }}
    toolbar: '#toolbar'
  }
});
複製代碼

方式二:對象數組

var quill = new Quill('#editor', {
  modules: {
    toolbar: {
        container:'#toolbar'
    }
  }
});
複製代碼

方式三:數組bash

var quill = new Quill('#editor', {
  modules: {
    toolbar: ['bold', 'italic', 'underline', 'strike']
  }
});
複製代碼

編輯區和工具欄區

從這些配置能夠看出,整個富文本分爲:編輯區和工具欄區。 #editor 掛載的元素目標將會被替換成輸入框的部分,而#toolbar 掛載的元素目標將會被替換成:工具欄區。app

var editor = new Quill('#editor', {
    modules: {
    toolbar: {
        container:'#toolbar'
    }
  }
});
複製代碼

定製化功能

經過上面簡單的配置,能夠使用一些基礎的富文本編輯器的功能,來實現Quill 功能的定製化less

Quill自帶功能如何實現

font 功能實現,分爲兩步:dom

  1. 實例化 Parchment.Attributor.Class 類,前面介紹中知道 Parchment.Attributor 屬性下有一個基礎類Attributor 和三個功能類class,style,store
  2. 咱們經過類實例化出一個 blots 節點,以後咱們須要 registerd 這個節點
// 步驟1
import Parchment from 'parchment';

let config = {
  scope: Parchment.Scope.INLINE,
  whitelist: ['serif', 'monospace']
};

let FontClass = new Parchment.Attributor.Class('font', 'ql-font', config);

export { FontClass };
// 步驟2 
import { FontClass } from './formats/font';
Quill.register({
'formats/font': FontClass,
},true)
複製代碼

自定義行高功能

quill 自己是不支持行高的功能的,須要咱們自定義這個功能,模仿Quill本地 font 功能的實現以下:codepen 自定義行高demo

const config = {
        scope: Parchment.Scope.INLINE,
        whitelist: this.lineHeightList
}
const Parchment = Quill.import("parchment");
    // 步驟一實例化 lineHeight 類(自定義的)
    class lineHeightAttributor extends Parchment.Attributor.Class {}
    const lineHeightStyle = new lineHeightAttributor(
      "lineHeight",
      "ql-lineHeight",
       config
    );
    // 註冊實例
    Quill.register({ "formats/lineHeight": lineHeightStyle }, true);
複製代碼

註冊完實例後,該如何使用行高的功能?

  1. 因爲咱們繼承的是 Parchment.Attributor.Class 它是會經過操做節點類名來實現節點的樣式的控制,因此能夠經過自定義的 ql-lineHeightclass 來控制選中文本的行高
<div id="app">
  <div id="toolbar">
    <select class="ql-lineHeight">
        // 經過 selected 來設置默認選中的行高樣式
        <option v-for="(lineHeight,index) in lineHeightList" :key="lineHeight" :value="lineHeight" :selected="index === 3">{{ lineHeight }}</option>
      </select>
  </div>
  <div id="container"></div>
</div>
複製代碼
  1. 自定義的dom結構定義好了之後,咱們須要自定義這些樣式對應的行高,

查看控制檯 Elements 能夠看到,Quill 會根據咱們設定的 select 去生成一個自定樣式的下拉框,例如 2.0 的行高樣式咱們能夠設置以下css樣式

// 該類名對應的行高
.ql-lineHeight-2 {
  line-height: 2;
}
// 設置 option 下拉框中選項的樣式
.ql-picker.ql-lineHeight .ql-picker-label[data-value="2"]::before,
// 設置 選中 option 後顯示的樣式
.ql-picker.ql-lineHeight .ql-picker-item[data-value="2"]::before {
  content: "2";
}
複製代碼

自定義字體大小

  • whitelist 子項類型必須爲 String 類型,不然會致使選中子項後沒有應用上對應都類名
data(){
    return {
        // whitelist 子項類型必須爲 String 類型,不然會致使選中子項後沒有應用上對應都類名
        sizeList:Array.from(Array(58),(item,index)=>String(index+12)),
    }
}
const Parchment = Quill.import("parchment")
      class Font extends Parchment.Attributor.Class{}
      const FontStyle = new Font('size','ql-size',{
        scope:Parchment.Scope.INLINE,
        whitelist:this.sizeList
      })
      Quill.register({
        'formats/size':FontStyle
      },true)
// 使用 less 中 range 和 each 方法來減小重複 css 代碼
@list:range(11,70,1);
each(@list,{
  .ql-size-@{value}{
    font-size:@value*1px;
  }
})
複製代碼

更多demo中細節代碼能夠點擊查看

事件

text-change 事件

this.quill.on('text-change',function(delta,oldDelta,source){
    console.log('delata',delta,oldDelta,source);
})
複製代碼

  • 能夠看到 delta 數據對象中包含 :

    1. retain:保留以前多少位數的數據
    2. insert:插入的數據
  • source 表示事件觸發來源,若是是用戶觸發的爲 user 若是是api操做的爲 api

其餘事件

事件類型:能夠經過 on(name: String, handler: Function): Quill quill.on()來註冊事件,如:text-change,editor-change
添加自定義事件:

// 獲取到 toolbar 操做對象
let toolbar = quill.getModule('toolbar');
toolbar.addHandler('image', ()=>{
      // 添加點擊圖片觸發的邏輯
    });
複製代碼

更多事件查看

Quill 源碼

瞭解瞭如何 Quill的基礎使用方法,以及如何定製一些功能後,接着來查看下 Quill的源碼結構

構造函數

路徑爲 /core/quill.js ,能夠看到構造函數上掛載了不少靜態方法和原型上的方法,來了解下一些經常使用的方法的實現。

class Quill {
    static debug(limit) {
    if (limit === true) {
      limit = 'log';
    }
    logger.level(limit);
  }

  static find(node) {
    return node.__quill || Parchment.find(node);
  }

  static import(name) {
    if (this.imports[name] == null) {
      debug.error(`Cannot import ${name}. Are you sure it was registered?`);
    }
    return this.imports[name];
  }

  static register(path, target, overwrite = false) {
  }
  // ...
}
複製代碼

Quill.import

好比咱們能夠經過 Quill.import去調用靜態方法 import,去執行 this.imports[name] Quill.imports 對象上默認掛載來如下四個模塊

Quill.imports = {
  'delta'       : Delta,
  'parchment'   : Parchment,
  'core/module' : Module,
  'core/theme'  : Theme
};
複製代碼

Quill.register

若是要添加增模塊,能夠經過 register 方法來註冊新的路徑及對應的模塊, 咱們使用時代碼一般以下

class LinkBlot extends Inline {}
LinkBlot.blotName = 'link';
LinkBlot.tagName = 'a';
Quill.register(LinkBlot);
複製代碼

來看看 Quill 源碼中的執行,下面方法中執行順序

  1. this.register('formats/' + 'link', LinkBlot, undefined);
  2. this.imports['formats/link'] = LinkBlot
  3. 最終經過 Parchment.register(LinkBlot) 註冊了模塊
class Quill{
    static register(path, target, overwrite = false) {
        if (typeof path !== 'string') {
          let name = path.attrName || path.blotName;
          if (typeof name === 'string') {
            // register(Blot | Attributor, overwrite)
            this.register('formats/' + name, path, target);
          } else {
            Object.keys(path).forEach((key) => {
              this.register(key, path[key], target);
            });
          }
        }else {
          if (this.imports[path] != null && !overwrite) {
            debug.warn(`Overwriting ${path} with`, target);
          }
          this.imports[path] = target;
          if ((path.startsWith('blots/') || path.startsWith('formats/')) &&
              target.blotName !== 'abstract') {
            Parchment.register(target);
          } else if (path.startsWith('modules') && typeof target.register === 'function') {
            target.register();
          }
        }
    }
 }
複製代碼

Quill font 功能內部實現

分爲兩步:

  1. 實例化 Parchment.Attributor.Class 類,前面介紹中知道 Parchment.Attributor 屬性下有一個基礎類Attributor 和三個功能類class,style,store
  2. 使用 Quill.register() 註冊實例
import Parchment from 'parchment';

let config = {
  scope: Parchment.Scope.INLINE,
  whitelist: ['serif', 'monospace']
};

let FontClass = new Parchment.Attributor.Class('font', 'ql-font', config);

export { FontClass };
複製代碼
  1. 首先實例化 Parchment.Attributor.Class 構造函數,以前查看過 src/attributor/class.ts 實例化改類就是執行 constructor 構造函數
class ClassAttributor extends Attributor {}
複製代碼
  1. 因爲 class 類繼承自 Attributor 因此查看 src/attributor/attributor.ts
export default class Attributor {
  constructor(attrName: string, keyName: string, options: AttributorOptions = {}) {}
 }
複製代碼

因此咱們執行 let FontClass = new Parchment.Attributor.Class('font', 'ql-font', config);時,就是想這個構造函數中傳入這三個參數。
3. 這行代碼作了什麼

constructor(attrName: string, keyName: string, options: AttributorOptions = {}) {
    // 記錄屬性名和類名
    this.attrName = attrName;
    this.keyName = keyName;
    let attributeBit = Registry.Scope.TYPE & Registry.Scope.ATTRIBUTE;
    // 判斷是否認義 scope 屬性
    if (options.scope != null) {
      // 因爲它是 scope 默認是經過二進制來表示的,因此,這裏採用位運算來判斷
      this.scope = (options.scope & Registry.Scope.LEVEL) | attributeBit;
    } else {
      // 若是沒有設置scope,則默認使用 ATTRIBUTE 二進制
      this.scope = Registry.Scope.ATTRIBUTE;
    }
    if (options.whitelist != null) this.whitelist = options.whitelist;
  }
複製代碼

能夠查看 registry.ts 源碼 路徑 src/registry.ts 定義了 Scope的可選值以下,scope 決定了 Blot 的類型是行內仍是塊級元素 關於哪些操做是行內,塊級,Embeds 能夠點擊此處查看

export enum Scope {
 TYPE = (1 << 2) - 1, // 0011 Lower two bits
 LEVEL = ((1 << 2) - 1) << 2, // 1100 Higher two bits

 ATTRIBUTE = (1 << 0) | LEVEL, // 1101
 BLOT = (1 << 1) | LEVEL, // 1110
 INLINE = (1 << 2) | TYPE, // 0111
 BLOCK = (1 << 3) | TYPE, // 1011

 BLOCK_BLOT = BLOCK & BLOT, // 1010
 INLINE_BLOT = INLINE & BLOT, // 0110
 BLOCK_ATTRIBUTE = BLOCK & ATTRIBUTE, // 1001
 INLINE_ATTRIBUTE = INLINE & ATTRIBUTE, // 0101

 ANY = TYPE | LEVEL,
}
複製代碼

Quill 其餘 api 方法

點擊官方文檔查看 這些方法均可以在 core/quill.js 文件中查看到,查看它如何實現

parchment 和 Deltas

在 Quill 中操做文檔模型和描述富文本內容 分別基於 Parchment 和 Delta,基於這二者,Quill纔可以經過 API 來操做富文本樣式,定製化和擴展富文本功能。二者功能以下:

  • Parchment 使用 Blots 來代替 dom 描述文檔,Parchment 主要做用就是操做文檔模型,咱們能夠經過其提供的接口進行 DOM 初始化,返回指定格式或者指定標籤和做用域等等。
  • 經過實例化 delta 會將咱們傳入的參數配置項掛載到 ops 下,而且這個實例原型上掛載了不少可用的方法,來操做文檔

parchment 源碼

主文件

路徑:src/Parchment.ts ,

let Parchment = {
  Scope: Registry.Scope,
  create: Registry.create,
  register: Registry.register,

  Container: ContainerBlot,
  Format: FormatBlot,
  Embed: EmbedBlot,

  Scroll: ScrollBlot,
  Block: BlockBlot,
  Inline: InlineBlot,
  Text: TextBlot,
  Attributor: {
    Attribute: Attributor,
    Class: ClassAttributor,
    Style: StyleAttributor,
    Store: AttributorStore,
  },
}
複製代碼

更多參數屬性查看

樣式管理模塊

attributor 文件夾下放一些節點屬性的設置方法。路徑爲:src/Parchment.ts文件暴露出的對象包含了全部 parchment 提供全部的方法。

import Attributor from './attributor/attributor';
import ClassAttributor from './attributor/class';
import StyleAttributor from './attributor/style';
import AttributorStore from './attributor/store';
let Parchment = {
    Attributor: {
        Attribute: Attributor,
        Class: ClassAttributor,
        Style: StyleAttributor,
        Store: AttributorStore,
    },
}
複製代碼

那style舉例,能夠看下 Attributor 如何實現 節點的 style 管理。

class StyleAttributor extends Attributor {
  add(node: HTMLElement, value: string): boolean {
    if (!this.canAdd(node, value)) return false;
    // @ts-ignore
    node.style[camelize(this.keyName)] = value;
    return true;
  }
  remove(node: HTMLElement): void {
    // @ts-ignore
    node.style[camelize(this.keyName)] = '';
    if (!node.getAttribute('style')) {
      node.removeAttribute('style');
    }
  }
  value(node: HTMLElement): string {
    // @ts-ignore
    let value = node.style[camelize(this.keyName)];
    return this.canAdd(node, value) ? value : '';
  }
}
複製代碼

實現方式其實也很簡單,就是經過 element.style.color = '#f00'這樣的格式給元素設置樣式。

  • class.ts 用於設置 class
  • store.ts 用於設置 attribute 記錄樣式格式以下,attributes 記錄了樣式。以下結構中的 attributes用於操做屬性的變化
{
  ops: [
    { insert: 'Gandalf', attributes: { bold: true } },
    { insert: 'Grey', attributes: { color: '#cccccc' } }
  ]
}
複製代碼
  • attributor.ts 至關於其餘三個類的基礎類,定義了一些公共的屬性和方法。
export default class Attributor {
  attrName: string;
  keyName: string;
  scope: Registry.Scope;
  whitelist: string[] | undefined;
 }
複製代碼

元素管理模塊

路徑爲:src/blot/abstract 文件夾下的結構以下

這些文件中分別定義了不少基礎通用的方法,如format.ts 文件中定義了一些格式化的方法以下

class FormatBlot extends ContainerBlot implements Formattable {
    format(){}
    formats(){}
    replaceWith(){}
    update(){}
    wrap(){}
}
複製代碼

而後在路徑爲 src/blot/blot.ts中引入了 format.ts 文件使用,經過繼承來實現 FormatBlot 中邏輯的複用

import FormatBlot from './abstract/format';
class BlockBlot extends FormatBlot {
  static blotName = 'block';
  static scope = Registry.Scope.BLOCK_BLOT;
  static tagName = 'P';
  format(name: string, value: any) {
    if (Registry.query(name, Registry.Scope.BLOCK) == null) {
      return;
    } else if (name === this.statics.blotName && !value) {
      this.replaceWith(BlockBlot.blotName);
    } else {
      super.format(name, value);
    }
  }
}
複製代碼

Blots

Parchment 來代替 dom 來描述文檔,Blots 就至關於元素,下面是關於 Blots 的定義

// 擁有如下字段和靜態方法,以及這些屬性的類型
class Blot {
  static blotName: string;
  static className: string;
  static tagName: string;
  //  inline or block
  static scope: Scope;
  domNode: Node;
  prev: Blot;
  next: Blot;
  parent: Blot;
  // Creates corresponding DOM node
  static create(value?: any): Node;
  // Apply format to blot. Should not pass onto child or other blot.
  format(format: name, value: any);
  insertAt(index: number, text: string);
  // ... 
}
複製代碼

Deltas

var delta = new Delta([
  { insert: 'Gandalf', attributes: { bold: true } }
]);
複製代碼

經過實例化 delta 會將咱們傳入的參數配置項掛載到 ops 下,而且這個實例原型上掛載了不少可用的方法。

生成文本

var delta = new Delta([
  { insert: 'Gandalf', attributes: { bold: true } },
  { insert: ' the ' },
  { insert: 'Grey', attributes: { color: '#ccc' } }
]);
複製代碼

Delta 是用來描述富文本內容的一種簡單的 JSON 格式, 上面的實例表示:會生成一個Gandalf the Grey 字符串,而且 Gandalf 字樣爲 bold , Grey 的字體顏色爲 #ccc

api 修改文本

上面咱們經過 Deltas 提供的 api 來修改文檔 :

var death = new Delta().retain(12).delete(4).insert('White', { color: '#fff' });
複製代碼

描述上面行爲的 json 以下,delta 保留原先字符串的前12位,在此之上刪除後四位,而後插入 White 字符串字體樣式爲白色

{
  ops: [
    { retain: 12 },
    { delete: 4 },
    { insert: 'White', attributes: { color: '#fff' } }
  ]
}
複製代碼
相關文章
相關標籤/搜索