富文本是给输入的内容增长样式,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 } }
]);
复制代码
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' } }
]
}
复制代码