富文本编辑器 quill.js 开发: 自定义格式扩展
Posted Grewer
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了富文本编辑器 quill.js 开发: 自定义格式扩展相关的知识,希望对你有一定的参考价值。
前言
鉴于各种繁杂的需求,quill.js
编辑器也面临着各种挑战,例如我们需要添加“table”布局样式以适应邮件发送格式,手动扩展表情符号功能等等。本文将对这些可定制化功能进行讲解和实现。
区分 format 和 module
首先需要明确的是,我们应该清楚自己所需的扩展具体是什么?
比如想要新增一个自定义 emoji, 那么想象一下步骤:
- 点击工具栏
- 弹出弹窗或者对应的 popover
- 在 2 中选中 emoji
这些步骤是一种常见的添加流程。
我们需要明确的是,添加自定义表情符号必然需要一个相应的格式。
本文将以 format
为例,对此进行详细讲解。
quill 的格式类型
说起 quill 的格式类型, 他的常用格式可以分成 3 类:
- Inline
常见的有Bold
,Color
,Font
等等, 不占据一行的标签, 类似于 html 里 span 的特性, 是一个行内样式,Inline
格式之间可以相互影响 - Block
添加Block
样式, 必然会占据一整行, 并且Block
样式之间不能兼容(共存), 常见的有List
,Header
,Code Block
等等 - Embeds
媒体文件, 常见的有Image
,Video
,Formula
, 这类格式扩展的比较少, 但是本次要加的emoji
但是这种格式
自定义样式
新增 emoji.ts 文件来存储格式, 关于他的类型, 我们选择 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
, 来消除歧义
一般来说, 新开发的扩展性类型, 尽量都加上 className
这样一个 emoji
类型就创建完成了!
最后我们注册到 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-* 来传递一些数据
quill.insertEmbed(range.index, \'emoji\',
alt: img.alt,
src: img.src,
width: img.width,
height: img.height,
);
quill.setSelection(range.index + 1);
展示下新增 emoji
的效果:
基础格式说明
我们的自定义格式都是基于 quill
的基础库: parchment
这里我们就介绍下他的几个重要 API
:
class Blot
// 在手动创建/初始值时, 都会触发 create 函数
static create(value?: any): Node;
// 从 domNode 上获取想要的数据
static formats(domNode: Node);
// static formats 返回的数据会被传递给 format
// 此函数的作用是将数据设置到 domNode
// 如果 name 是 quill 里的格式走默认逻辑是会被正确使用的
// 如果是特殊的name, 不处理就不会起效
format(format: name, value: any);
// 返回一个值, 通常在初始化的时候传给 static create
// 通常实现一个自定义格式, value 和 format 使用一个即可达到目标
value(): any;
上述几个 API
便是创建自定义格式时常用到的
在上文讲到了 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);
目前来说, 这两种方案都能实现我们的 EmojiBlot
当然 format
的作用, 并不仅仅在于 新增属性到 dom 上, 也可以针对某些属性, 修改、删除 dom 上的信息
其他格式
上面我们讲述了三个常见的格式: Inline
、Embeds
、Block
, 其实在 quill
还有一些特殊的 blot
:
如: TextBlot
、 ContainerBlot
、 ScrollBlot
其中 ScrollBlot
属于是所有 blot
的根节点:
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
的格式下, 他的子节点, 只能是文本, 换行, 光标
(换行符和光标都属于 EmbedBlot
)
这样就控制住了子节点的类型, 避免结构错乱
ContainerBlot
最后要说一下 ContainerBlot
, 这是一个在自定义节点时, 创建 Block
类型时经常会用到的值:
在源码中, 并没有默认的子节点配置, 所以导致看上去就像这样, 但其实 container
的自由度是非常强的
这里就给出一个我之前创建的信件格式例子:
在富文本中扩展格式生成能兼容大部分信件的外层格式, 格式要求:
格式占据一定宽度, 如 500px, 需要让这部分居中, 格式内可以输入其他的样式
大家可能觉得简单, 只需要 div
套上, 再加上一个样式 width
和 text-align
即可
但是这种方案不太适合邮件的场景, 在桌面和移动端渲染电子邮件大约有上百万种不同的组合方式。
所以最稳定的布局方案只有 table
布局
所以我们开始创建一个 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
标签, 那么同样也会需要 tr
和 rd
:
也是类似的创建方法:
class WidthFormatTR extends Container
class WidthFormatTD extends Container
最后通过 API 将其关联起来:
WidthFormatTable.allowedChildren = [WidthFormatTR];
WidthFormatTR.allowedChildren = [WidthFormatTD];
WidthFormatTR.requiredContainer = WidthFormatTable;
WidthFormatTD.requiredContainer = WidthFormatTR;
WidthFormatTD.allowedChildren = [WidthFormat];
WidthFormat.requiredContainer = WidthFormatTD;
这一段的含义就是, 保证各个格式的父元素与子元素分别是什么, 不会出现乱套的情况
格式中最后的主体:
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
函数的作用就是在注册当前的 WidthFormat
格式时, 自动注册其他的依赖格式; 避免人多注册多次
最后我们新增一个按钮, 来格式化编辑器内容:
const widthFormatHandle = () =>
const editor = getEditor();
editor.format(\'width-format\', )
展示下效果:
比较遗憾的是, 同样作为 Block
格式, 这两类是不能兼容的, 也就是说在 width-format
格式中, 不能使用 List
, Header
, Code
这几项属性
个人吐槽几句, 之前尝试兼容过, 但是在 HTML
和 delta
相互转换时被卡主了, 感觉转换的方式没做好
总结
demo链接: 点此查看
本文介绍了 quill.js 在面临多种需求挑战时需要添加可定制化功能。quill.js 的常用格式包括 Inline、Block 和 Embeds 三类,而
ContainerBlot 则是创建 Block 类型时常用的值,具有极高的自由度。希望本文能够帮助读者更好地了解和思考富文本编辑的相关问题。
轻量级quill富文本编辑器
因为公司产品需要在移动端编辑文本,所以发现了这个轻量级的好东西,网上也没找到比较好的案例,就自己总结了下,有兴趣的直接复制代码运行看看就知道啦!
下面是quill.js的CDN加速地址: <!-- Main Quill library --> <script src="//cdn.quilljs.com/1.0.3/quill.js" type="text/javascript"></script> <script src="//cdn.quilljs.com/1.0.3/quill.min.js" type="text/javascript"></script> <!-- Theme included stylesheets --> <link href="//cdn.quilljs.com/1.0.3/quill.snow.css" rel="stylesheet"> <link href="//cdn.quilljs.com/1.0.3/quill.bubble.css" rel="stylesheet"> <!-- Core build with no theme, formatting, non-essential modules --> <link href="//cdn.quilljs.com/1.0.3/quill.core.css" rel="stylesheet"> <script src="//cdn.quilljs.com/1.0.3/quill.core.js" type="text/javascript"></script>
多的不讲了,直接上代码。里面有注释,这些都是基础的,大神绕道,嘿嘿
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>文本编辑器</title> <meta name="viewport" content="width=device-width, initial-scale=1.0,maximum-scale=1.0, user-scalable=no"/> <link href="http://cdn.quilljs.com/1.0.0/quill.snow.css" rel="stylesheet"> <script src="http://cdn.quilljs.com/1.0.0/quill.js"></script> <style> /*编辑器样式修改*/ .ql-snow .ql-picker.ql-size .ql-picker-label::before { content: ‘中字体‘; } .ql-toolbar.ql-snow .ql-formats { margin-right: 8px; } #editor{min-height:300px;} </style> </head> <body> <!-- 创建工具栏组件 --> <div id="toolbar"> <span class="ql-formats"> <button class="ql-bold">Bold</button><!--控制粗细--> <button class="ql-italic">Italic</button> <!--控制切斜--> <button class="ql-underline">下划线</button> <!--下划线--> <button class="ql-link">link</button> <!--链接--> </span> <span class="ql-formats"> <button class="ql-list" value="ordered"></button><!--序号--> <button class="ql-list" value="bullet"></button> <!--点--> <button class="ql-list" value="ql-blockquote"></button> <!--引用--> <button class="ql-code-block"></button> <!--代码--> <button class="ql-image" value="bullet"></button> <!--图片--> </span> <span class="ql-formats"> <select class="ql-color"> <option selected></option> <option value="red"></option> <option value="orange"></option> <option value="yellow"></option> <option value="green"></option> <option value="blue"></option> <option value="purple"></option> </select> <select class="ql-background"> <option selected></option> <option value="red"></option> <option value="orange"></option> <option value="yellow"></option> <option value="green"></option> <option value="blue"></option> <option value="purple"></option> </select> </span> <span class="ql-formats"><!--控制大小--> <select class="ql-size"> <option value="10px">小字体</option> <option selected>中字体</option> <option value="18px">大字体</option> <option value="32px">超大字</option> </select> </span> </div> <!-- 创建文本编辑器 --> <div id="editor"> <p>Hello World!</p> </div> <script> window.onload=function(){ var BackgroundClass = Quill.import(‘attributors/class/background‘); var ColorClass = Quill.import(‘attributors/class/color‘); var SizeStyle = Quill.import(‘attributors/style/size‘); Quill.register(BackgroundClass, true); Quill.register(ColorClass, true); Quill.register(SizeStyle, true); var editor = new Quill(‘#editor‘, { modules: { toolbar: ‘#toolbar‘ }, placeholder: ‘Compose an epic...‘, theme: ‘snow‘ }); } </script> </body> </html>
以上是关于富文本编辑器 quill.js 开发: 自定义格式扩展的主要内容,如果未能解决你的问题,请参考以下文章