富文本是給輸入的內容增長樣式,Quill 提供 api 的方式去判斷修改 DOM ,而不像不少其餘的編輯器經過遍歷 DOM 樹的方式來作到這點。而且 Quill 支持定製化功能,接下來來了解下如何使用 Quill 及它的相關源碼。css
如同 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'
};
複製代碼
用於配置工具欄
方式一: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
font 功能實現,分爲兩步:dom
Attributor
和三個功能類class
,style
,store
// 步驟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);
複製代碼
註冊完實例後,該如何使用行高的功能?
Parchment.Attributor.Class
它是會經過操做節點類名來實現節點的樣式的控制,因此能夠經過自定義的 ql-lineHeight
class 來控制選中文本的行高
<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>
複製代碼
// 該類名對應的行高
.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";
}
複製代碼
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中細節代碼能夠點擊查看
this.quill.on('text-change',function(delta,oldDelta,source){
console.log('delata',delta,oldDelta,source);
})
複製代碼
能夠看到 delta 數據對象中包含 :
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的源碼結構
路徑爲 /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
去調用靜態方法 import
,去執行 this.imports[name]
Quill.imports 對象上默認掛載來如下四個模塊
Quill.imports = {
'delta' : Delta,
'parchment' : Parchment,
'core/module' : Module,
'core/theme' : Theme
};
複製代碼
若是要添加增模塊,能夠經過 register
方法來註冊新的路徑及對應的模塊, 咱們使用時代碼一般以下
class LinkBlot extends Inline {}
LinkBlot.blotName = 'link';
LinkBlot.tagName = 'a';
Quill.register(LinkBlot);
複製代碼
來看看 Quill 源碼中的執行,下面方法中執行順序
this.register('formats/' + 'link', LinkBlot, undefined);
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();
}
}
}
}
複製代碼
分爲兩步:
Attributor
和三個功能類class
,style
,store
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 };
複製代碼
src/attributor/class.ts
實例化改類就是執行 constructor 構造函數class ClassAttributor extends 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,
}
複製代碼
點擊官方文檔查看 這些方法均可以在 core/quill.js
文件中查看到,查看它如何實現
在 Quill 中操做文檔模型和描述富文本內容 分別基於 Parchment 和 Delta,基於這二者,Quill纔可以經過 API 來操做富文本樣式,定製化和擴展富文本功能。
二者功能以下:
參數配置項掛載到 ops
下,而且這個實例原型上掛載了不少可用的方法,來操做文檔
路徑: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
用於設置 classstore.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);
}
}
}
複製代碼
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);
// ...
}
複製代碼
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
上面咱們經過 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' } }
]
}
複製代碼