DevUI 是一款面向企業中后台產品的開源前端解決方案,它倡導沉浸
、靈活
、至簡
的設計價值觀,提倡設計者為真實的需求服務,為多數人的設計,拒絕嘩眾取寵、取悅眼球的設計。如果你正在開發 ToB
的工具類產品
,DevUI 將是一個很不錯的選擇!
引言
富文本編輯器大概是最復雜、使用場景卻極廣的組件了。
可以說富文本編輯器讓Web數據錄入充滿了無限的想象空間,如果只有文本框、下拉框這些純文本的數據錄入組件,那么Web的數據錄入能力將極大地受限。我們將無法在網頁上插入圖片、視頻這些富文本內容,更無法插入自定義的內容。
富文本編輯器讓Web內容編輯變得更輕松、更高效,我們幾乎可以在富文本編輯器中插入任何你想插入的內容,圖片、視頻、超鏈接、公式、代碼塊,都不在話下,甚至還可以插入表格、PPT、思維導圖,甚至3D模型這種超復雜的自定義內容。
富文本編輯器的場景在Web上也是隨處可見,寫文章、寫評論、意見反饋、錄需求單,都需要使用到富文本。
本文結合DevUI團隊在富文本組件中的實踐,從使用場景、技術選型,再到對Quill的擴展,以及Quill的基本原理,跟大家分享Quill富文本編輯器的那些事兒。
本文主要由以下部分組成:
- 富文本編輯器的使用場景
- 技術選型
- 我們為什么選擇Quill
- 如何擴展Quill
- Quill基本原理
以下內容來自Kagol
在華為 HWEB 大前端技術分享會
上的演講。
富文本編輯器的使用場景
- 博客文章
- Wiki詞條
- 工作項描述
- 測試用例步驟
- 反饋意見
- 評論
- …
技術選型
我們的需求:
- 開源協議友好
- Angular框架或框架無關
- 靈活可擴展
- 支持插入/編輯表格和圖片
- 插件豐富,生態好
選型分析
- 首先排除官方不維護的
UEditor
- 然后排除React框架專屬的
Draft.js
和Slate
- 接着排除開源協議不友好的
CKEditor
- 由於我們的業務場景豐富,需要富文本插入/編輯表格的功能,所以還需要排除不支持表格的
Trix
,弱支持表格的Etherpad
和Prosemirror
,以及表格功能收費的TinyMCE
- 最后只剩下
Quill
和wangEditor
兩款編輯器可選,wangEditor
的擴展性和生態不如Quill
,所以最終選擇Quill
作為富文本組件的基座
為什么選擇Quill?
- BSD協議,商業友好
- 文檔詳細,上手快
- API驅動,擴展性好
- 插件豐富,生態好
文檔詳細
Document:https://quilljs.com/
介紹Quill的API:
介紹如何擴展Quill:
上手快
- 安裝Quill:
npm i quill
- 引入樣式:
@import 'quill/dist/quill.snow.css';
- 引入Quill:
import Quill from 'quill';
- 初始化Quill:
new Quill('#editor', { theme: 'snow' });
效果圖:
API驅動,擴展性好
插件豐富,生態好
擴展Quill
插入標簽
比如我想在編輯器里插入標簽
上傳附件
比如我想在編輯器里插入附件
插入表情
比如我想在編輯器中插入表情
類似語雀的評論:https://www.yuque.com/yuque/blog/sguhed
個性分割線
比如我想插入B站這種個性化的分割線
超鏈接卡片
比如我想插入知乎這樣的超鏈接卡片
如何插入表情?
我們從如何插入表情入手,一起看看怎么在Quill中插入自定義的內容。
要在Quill中插入表情,只需要以下四步:
- 第一步:自定義工具欄按鈕
- 第二步:自定義Blot內容EmojiBlot
- 第三步:在Quill注冊EmojiBlot
- 第四步:調用Quill的API插入表情
第一步:自定義工具欄按鈕
const quill = new Quill('#editor', {
theme: 'snow',
modules: {
// 配置工具欄模塊
toolbar: {
container: [ …, [ 'emoji' ] ], // 增加一個按鈕
handlers: {
// 添加按鈕的處理邏輯
emoji() {
console.log('插入表情');
}
}
},
}
});
給工具欄按鈕增加圖標
// 擴展Quill內置的icons配置
const icons = Quill.import('ui/icons');
icons.emoji = ‘<svg>…</svg>’; // 圖標的svg可以從iconfont網站復制
效果如下:
工具欄上已經多了一個表情的按鈕,並且能夠響應鼠標點擊事件,下一步就是要
編寫插入表情的具體邏輯,這涉及到Quill的自定義內容相關的知識。
第二步:自定義Blot內容EmojiBlot
Quill中的Blot就是一個普通的ES6 Class,由於表情和圖片的差別就在於:
Quill內置的圖片格式不支持自定義寬高,而我們要插入的表情是需要特定的寬高的。
因此我們可以基於Quill內置的image格式來擴展。
emoji.ts
import Quill from 'quill';
const ImageBlot = Quill.import('formats/image');
// 擴展Quill內置的image格式
class EmojiBlot extends ImageBlot {
static blotName = 'emoji'; // 定義自定義Blot的名字(必須全局唯一)
static tagName = 'img'; // 自定義內容的標簽名
// 創建自定義內容的DOM節點
static create(value): any {
const node = super.create(value);
node.setAttribute('src', ImageBlot.sanitize(value.url));
if (value.width !== undefined) {
node.setAttribute('width', value.width);
}
if (value.height !== undefined) {
node.setAttribute('height', value.height);
}
return node;
}
// 返回options數據
static value(node): any {
return {
url: node.getAttribute('src'),
width: node.getAttribute('width'),
height: node.getAttribute('height')
};
}
}
export default EmojiBlot;
第三步:在Quill注冊EmojiBlot
有了EmojiBlot,要將其插入Quill編輯器中,還需要將這個ES6類注冊到Quill中。
import EmojiBlot from './formats/emoji';
Quill.register('formats/emoji', EmojiBlot);
第四步:調用Quill的API插入表情
EmojiBlot注冊到Quill中之后,Quill就能認識它了,也就可以調用Quill的API將其插入到編輯器中。
emoji(): void {
console.log(‘插入表情');
// 獲取當前光標位置
const index = this.quill.getSelection().index;
// 在當前光標處插入emoji(blotName)
this.quill.insertEmbed(index, 'emoji', {
url: 'assets/emoji/good.png',
width: '64px',
});
},
效果圖
Demo源碼
源碼鏈接:https://gitee.com/kagol/quill-demo
也歡迎關注我們DevUI組件庫的官網,了解更多有趣又實用的開源組件!
DevUI官網:https://devui.design
Quill基本原理
最后講一講Quill的基本原理。
基本原理
- 使用Delta數據模型描述富文本內容及其變化,以保證行為的可預測
- 通過Parchment對DOM進行抽象,以保證平台一致性
- 通過Mutation Observe監聽DOM節點的變化,將DOM的更改同步到Delta數據模型中
Quill如何表達編輯器內容?
Delta數據模型
通過Delta數據模型來描述富文本內容及其變化
Delta 是JSON的一個子集,只包含一個 ops 屬性,它的值是一個對象數組,每個數組項代表對編輯器的一個操作(以編輯器初始狀態為空為基准)。
{
"ops": [
{ "insert": "Hello " },
{ "insert": "World", "attributes": { "bold": true } },
{ "insert": "\n" }
]
}
修改編輯器內容
比如我們把加粗的"World"改成紅色的文字"World",這個動作用 Delta 描述如下:
{
"ops": [
{ "retain": 6 },
{ "retain": 5, "attributes": { "color": "#ff0000" } }
]
}
意思是:保留編輯器最前面的6個字符,即保留"Hello "不動,保留之后的5個字符"World",並將這些字符設置為字體顏色為"#ff0000"。
刪除編輯器內容
如果要刪除"World"呢?
{
"ops": [
{ "retain": 6 },
{ "delete": 5 }
]
}
即:保留前面6個字符(’Hello ’),刪除之后的5個字符(’World’)
Quill如何渲染內容?
渲染富文本內容的基本原理:
遍歷Delta數組,將其中描述的內容一個一個應用(插入/格式化/刪除)到編輯器中。
詳情可參考DevUI專欄文章:
Quill如何擴展編輯器的能力?
擴展Quill的方式:
- 通過自定義Blot格式來擴展編輯器的內容
- 通過自定義模塊來擴展編輯器的功能
詳情可參考DevUI專欄文章:
THANK YOU!