富文本編輯器quill.js開發(fā)之自定義格式擴展
前言
鑒于各種繁雜的需求,quill.js
編輯器也面臨著各種挑戰(zhàn),例如我們需要添加“table”布局樣式以適應(yīng)郵件發(fā)送格式,手動擴展表情符號功能等等。本文將對這些可定制化功能進(jìn)行講解和實現(xiàn)。
區(qū)分 format 和 module
首先需要明確的是,我們應(yīng)該清楚自己所需的擴展具體是什么?
比如想要新增一個自定義 emoji, 那么想象一下步驟:
- 點擊工具欄
- 彈出彈窗或者對應(yīng)的 popover
- 在 2 中選中 emoji
這些步驟是一種常見的添加流程。
我們需要明確的是,添加自定義表情符號必然需要一個相應(yīng)的格式。
本文將以 format
為例,對此進(jìn)行詳細(xì)講解。
quill 的格式類型
說起 quill 的格式類型, 他的常用格式可以分成 3 類:
- Inline
常見的有 Bold
, Color
, Font
等等, 不占據(jù)一行的標(biāo)簽, 類似于 html 里 span 的特性, 是一個行內(nèi)樣式, Inline
格式之間可以相互影響
- Block
添加 Block
樣式, 必然會占據(jù)一整行, 并且 Block
樣式之間不能兼容(共存), 常見的有 List
, Header
, Code Block
等等
- Embeds
媒體文件, 常見的有 Image
, Video
, Formula
, 這類格式擴展的比較少, 但是本次要加的 emoji
但是這種格式
自定義樣式
新增 emoji.ts 文件來存儲格式, 關(guān)于他的類型, 我們選擇 Embeds
格式, 使用這種格式有以下原因:
- 他是一種獨特的類型, 不能和顏色, 字體大小等等用在一起
- 需要和字體并列, 所以也不能是
Block
類型
import Quill from 'quill'; const Embed = Quill.import('blots/embed'); class EmojiBlot extends Embed { static blotName: string; static tagName: string; static create(value: HTMLImageElement) { const node = super.create(); node.setAttribute('alt', value.alt); node.setAttribute('src', value.src); node.setAttribute('width', value.width); node.setAttribute('height', value.height); return node; } static formats(node: HTMLImageElement) { return { alt: node.getAttribute('alt'), src: node.getAttribute('src'), width: node.getAttribute('width'), height: node.getAttribute('height'), }; } static value(node: HTMLImageElement) { // 主要在有初始值時起作用 return { alt: node.getAttribute('alt'), src: node.getAttribute('src'), width: node.getAttribute('width'), height: node.getAttribute('height'), }; } } EmojiBlot.blotName = 'emoji'; EmojiBlot.tagName = 'img'; EmojiBlot.className = 'emoji_icon' export default EmojiBlot;
因為還有正常的圖片類型會使用 img
, 這里就需要加上 className
, 來消除歧義
一般來說, 新開發(fā)的擴展性類型, 盡量都加上 className
這樣一個 emoji
類型就創(chuàng)建完成了!
最后我們注冊到 Quill
上即可:
import EmojiBlot from "./formats/emoji"; Quill.register(EmojiBlot);
這里我們在加上自定義的 popover
, 用來點擊獲取 emoji
:
<Popover content={<div className={'emoji-popover'} onClick={proxyEmojiClick}> <img alt={'圖片說明'} width={32} height={32} src="https://grewer.github.io/dataSave/emoji/img.png"/> <img alt={'圖片說明'} width={32} height={32} src="https://grewer.github.io/dataSave/emoji/img_1.png"/> </div>}> <button className="ql-emoji">emoji</button> </Popover>
通過代理的方式, 來獲取 dom
上的具體屬性:
const proxyEmojiClick = ev => { const img = ev.target if (img?.nodeName === 'IMG') { const quill = getEditor(); const range = quill.getSelection(); // 這里可以用 img 的屬性, 也可以通過 data-* 來傳遞一些數(shù)據(jù) quill.insertEmbed(range.index, 'emoji', { alt: img.alt, src: img.src, width: img.width, height: img.height, }); quill.setSelection(range.index + 1); } }
展示下新增 emoji
的效果:
基礎(chǔ)格式說明
我們的自定義格式都是基于 quill
的基礎(chǔ)庫: parchment
這里我們就介紹下他的幾個重要 API
:
class Blot { // 在手動創(chuàng)建/初始值時, 都會觸發(fā) create 函數(shù) static create(value?: any): Node; // 從 domNode 上獲取想要的數(shù)據(jù) static formats(domNode: Node); // static formats 返回的數(shù)據(jù)會被傳遞給 format // 此函數(shù)的作用是將數(shù)據(jù)設(shè)置到 domNode // 如果 name 是 quill 里的格式走默認(rèn)邏輯是會被正確使用的 // 如果是特殊的name, 不處理就不會起效 format(format: name, value: any); // 返回一個值, 通常在初始化的時候傳給 static create // 通常實現(xiàn)一個自定義格式, value 和 format 使用一個即可達(dá)到目標(biāo) value(): any; }
上述幾個 API
便是創(chuàng)建自定義格式時常用到的
詳情可參考: https://www.npmjs.com/package/parchment#blots
在上文講到了 format
和 value
的作用, 我們也可以對于 EmojiBlot
做出一些改造:
class EmojiBlot extends Embed { static blotName: string; static tagName: string; static create(value: HTMLImageElement) { const node = super.create(); node.setAttribute('alt', value.alt); node.setAttribute('src', value.src); node.setAttribute('width', value.width); node.setAttribute('height', value.height); return node; } static formats(node: HTMLImageElement) { return { alt: node.getAttribute('alt'), src: node.getAttribute('src'), width: node.getAttribute('width'), height: node.getAttribute('height'), }; } format(name, value) { if (['alt', 'src', 'width', 'height'].includes(name)) { this.domNode.setAttribute(name, value); } else { super.format(name, value); } } }
目前來說, 這兩種方案都能實現(xiàn)我們的 EmojiBlot
當(dāng)然 format
的作用, 并不僅僅在于 新增屬性到 dom 上, 也可以針對某些屬性, 修改、刪除 dom 上的信息
其他格式
上面我們講述了三個常見的格式: Inline
、Embeds
、Block
, 其實在 quill
還有一些特殊的 blot
:
如: TextBlot
、 ContainerBlot
、 ScrollBlot
其中 ScrollBlot
屬于是所有 blot
的根節(jié)點:
class Scroll extends ScrollBlot { // ... } Scroll.blotName = 'scroll'; Scroll.className = 'ql-editor'; Scroll.tagName = 'DIV'; Scroll.defaultChild = Block; Scroll.allowedChildren = [Block, BlockEmbed, Container];
至于 TextBlot
, 是在定義一些屬性時常用到的值:
例如源碼中 CodeBlock
的部分:
CodeBlock.allowedChildren = [TextBlot, Break, Cursor];
意味著 CodeBlock
的格式下, 他的子節(jié)點, 只能是文本, 換行, 光標(biāo)
(換行符和光標(biāo)都屬于 EmbedBlot
)
這樣就控制住了子節(jié)點的類型, 避免結(jié)構(gòu)錯亂
ContainerBlot
最后要說一下 ContainerBlot
, 這是一個在自定義節(jié)點時, 創(chuàng)建 Block
類型時經(jīng)常會用到的值:
在源碼中, 并沒有默認(rèn)的子節(jié)點配置, 所以導(dǎo)致看上去就像這樣, 但其實 container
的自由度是非常強的
這里就給出一個我之前創(chuàng)建的信件格式例子:
在富文本中擴展格式生成能兼容大部分信件的外層格式, 格式要求:
格式占據(jù)一定寬度, 如 500px, 需要讓這部分居中, 格式內(nèi)可以輸入其他的樣式
大家可能覺得簡單, 只需要 div
套上, 再加上一個樣式 width
和 text-align
即可
但是這種方案不太適合郵件的場景, 在桌面和移動端渲染電子郵件大約有上百萬種不同的組合方式。
所以最穩(wěn)定的布局方案只有 table
布局
所以我們開始創(chuàng)建一個 table
布局的外殼:
class WidthFormatTable extends Container { static create() { const node = super.create(); node.setAttribute('cellspacing', 0); node.setAttribute('align', 'center'); return node; } } WidthFormatTable.blotName = 'width-format-table'; WidthFormatTable.className = 'width-format-table'; WidthFormatTable.tagName = 'table';
有了 table
標(biāo)簽, 那么同樣也會需要 tr
和 rd
:
也是類似的創(chuàng)建方法:
class WidthFormatTR extends Container { } class WidthFormatTD extends Container { }
最后通過 API 將其關(guān)聯(lián)起來:
WidthFormatTable.allowedChildren = [WidthFormatTR]; WidthFormatTR.allowedChildren = [WidthFormatTD]; WidthFormatTR.requiredContainer = WidthFormatTable; WidthFormatTD.requiredContainer = WidthFormatTR; WidthFormatTD.allowedChildren = [WidthFormat]; WidthFormat.requiredContainer = WidthFormatTD;
這一段的含義就是, 保證各個格式的父元素與子元素分別是什么, 不會出現(xiàn)亂套的情況
格式中最后的主體:
class WidthFormat extends Block { static register() { Quill.register(WidthFormatTable); Quill.register(WidthFormatTR); Quill.register(WidthFormatTD); } } WidthFormat.blotName = 'width-format'; WidthFormat.className = 'width-format'; WidthFormat.tagName = 'div';
register
函數(shù)的作用就是在注冊當(dāng)前的 WidthFormat
格式時, 自動注冊其他的依賴格式; 避免人多注冊多次
最后我們新增一個按鈕, 來格式化編輯器內(nèi)容:
const widthFormatHandle = () => { const editor = getEditor(); editor.format('width-format', {}) }
展示下效果:
比較遺憾的是, 同樣作為 Block
格式, 這兩類是不能兼容的, 也就是說在 width-format
格式中, 不能使用 List
, Header
, Code
這幾項屬性
個人吐槽幾句, 之前嘗試兼容過, 但是在 HTML
和 delta
相互轉(zhuǎn)換時被卡主了, 感覺轉(zhuǎn)換的方式?jīng)]做好
總結(jié)
demo鏈接: 點此查看
本文介紹了 quill.js 在面臨多種需求挑戰(zhàn)時需要添加可定制化功能。quill.js 的常用格式包括 Inline、Block 和 Embeds 三類,而ContainerBlot 則是創(chuàng)建 Block 類型時常用的值,具有極高的自由度。希望本文能夠幫助讀者更好地了解和思考富文本編輯的相關(guān)問題。
以上就是富文本編輯器quill.js開發(fā)之自定義格式擴展的詳細(xì)內(nèi)容,更多關(guān)于富文本編輯器quill.js格式擴展的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
解決Vue中mounted鉤子函數(shù)獲取節(jié)點高度出錯問題
本篇文章給大家分享了如何解決Vue中mounted鉤子函數(shù)獲取節(jié)點高度出錯問題,對此有興趣的朋友可以參考學(xué)習(xí)下。2018-05-05Vue路由守衛(wèi)及頁面登錄權(quán)限控制的設(shè)置方法(兩種)
這篇文章主要介紹了Vue路由守衛(wèi)及頁面登錄權(quán)限控制的設(shè)置方法,本文通過實例代碼通過兩種方法給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-03-03vue3如何實現(xiàn)表格內(nèi)容無縫滾動(又寫了一堆冗余代碼)
這篇文章主要給大家介紹了關(guān)于vue3如何實現(xiàn)表格內(nèi)容無縫滾動的相關(guān)資料,在Vue3項目中難免會遇到讓列表滾動的需求,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下2024-04-04vue項目中在外部js文件中直接調(diào)用vue實例的方法比如說this
這篇文章主要介紹了vue項目中在外部js文件中直接調(diào)用vue實例的方法比如說this,需要的朋友可以參考下2019-04-04vue中的render函數(shù)、h()函數(shù)、函數(shù)式組件詳解
在vue中我們使用模板HTML語法來組建頁面的,使用render函數(shù)我們可以用js語言來構(gòu)建DOM,這篇文章主要介紹了vue中的render函數(shù)、h()函數(shù)、函數(shù)式組件,需要的朋友可以參考下2023-02-02vue3+vite項目配置ESlint、pritter插件過程
這篇文章主要介紹了vue3+vite項目配置ESlint、pritter插件過程,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-08-08Vue?處理異步加載順序問題之如何在Konva中確保文本在圖片之上顯示
在處理Konva中的異步加載順序問題時,確保在圖像加載完成后再添加其他元素是關(guān)鍵,通過將回調(diào)函數(shù)放在imageObj.onload中,并正確處理變量捕獲,我們可以確保文本總是繪制在圖片之上,這不僅解決了顯示順序的問題,也為未來的調(diào)試提供了明確的方向,感興趣的朋友一起看看吧2024-07-07在vue中使用inheritAttrs實現(xiàn)組件的擴展性介紹
這篇文章主要介紹了在vue中使用inheritAttrs實現(xiàn)組件的擴展性介紹,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-12-12