现代富文本编辑器Quill的内容渲染机制

前端开发 作者: 2024-08-20 13:25:01
本文主要介绍Quill内容渲染相关的基本原理,主要包括: 1. Quill描述编辑器内容的方式 2. Quill将Delta渲染到DOM的基本原理 3. Scroll类管理所有子Blot的基本原理

引言

  1. Quill描述编辑器内容的方式
  2. Quill将Delta渲染到DOM的基本原理
  3. Scroll类管理所有子Blot的基本原理

Quill如何描述编辑器内容?

1 <script>
2   var quill = new Quill('#editor,{
3     theme: snow'
4   });
5 </>
{
2   "ops": [
3     { "insert": "Hello " },4     { "insert": "World","attributes": { "bold": true } },1)">5     { "insert": "\n" }
6   ]
7 }
  • insert:插入
  • retain:保留
  • delete:删除
  • attributes:格式属性
3     { "retain": 64     { "retain": 5,"attributes": { "color": "#ff0000" } }
6 }
4     { "delete": 56 }
3     { "insert": { "image": "https://quilljs.com/assets/images/logo.svg"4     { "insert": "\n"6 }
{ 
: [ 
3     { "insert": { "formula": "e=mc^2" } 
6 }

setContent如何将Delta数据渲染成DOM?

1 const delta = {  "ops"2     { "insert": "Hello "3     { "insert": "World",1)"> } ]
5 }
1 const quill = new Quill('#editor'2   theme: 'snow'
3 });
1 quill.setContents(delta);
 1 setContents(delta,source = Emitter.sources.API) {
 2   return modify.call( this,() => {
 3     delta = new Delta(delta);
 4     const length = this.getLength();
 5     const deleted = this.editor.deleteText(0 6     const applied = .editor.applyDelta(delta);
 7     ... // 为了方便阅读,省略了非核心代码
 8     return deleted.compose(applied);
 9   },source,);
10 }
  1. 把编辑器里面原有的内容全部删除
  2. 应用传入的 Delta 数据,将其渲染到编辑器中
  3. 返回1和2组合之后的 Delta 数据
 1 applyDelta(delta) {
 2   let consumeNextNewline = false;
 3   .scroll.update();
 4   let scrollLength = .scroll.length();
 5   .scroll.batchStart();
 6   const normalizedDelta = normalizeDelta(delta);
 7 
 8   normalizedDelta.reduce((index,op) => 9     const length = op.retain || op.delete || op.insert.length || 110     let attributes = op.attributes || {};    
11      1.插入文本
12     if (op.insert != null) {
13       if (typeof op.insert === 'string') {        
14          普通文本内容
15         let text = op.insert;        
16         ...  为了阅读方便,省略非核心代码
17         .scroll.insertAt(index,text);
18         ... 19       } else typeof op.insert === 'object'20          富文本内容
21         const key = Object.keys(op.insert)[0];
22          There should only be one key
23         if (key == null)  index;
24         25       }
26       scrollLength += length;
27     }    
28      2.对文本进行格式化
29     Object.keys(attributes).forEach(name =>30       .scroll.formatAt(index,length,name,attributes[name]);
31     });
32     return index +33   },0);
34 ...  为了阅读方便,省略非核心代码  this.scroll.batchEnd();
35   .scroll.optimize();
36   return .update(normalizedDelta);
37 }
  1. 插入普通文本或富文本内容:insertAt
  2. 格式化该文本:formatAt
  1. setContents 方法本身没有什么逻辑,仅仅是调用了 modify 方法而已
  2. 在传入 modify 方法的匿名函数中调用了 Editor 对象的 applyDelta 方法
  3. applyDelta 方法对传入的 Delta 数据进行迭代,并依次插入/格式化/删除 Delta 数据所描述的编辑器内容

Scroll如何管理所有的Blot类型?

this.scroll = Parchment.create(.root,1)">2   emitter: .emitter,1)">3   whitelist: .options.formats
4 });
 1 export function create(input: Node | string | Scope,value?: any): Blot {
 传入的 input 就是编辑器主体 DOM 元素(.ql-editor),里面包含了编辑器里所有可编辑的实际内容   
 match 是通过 query 方法查询到的 Blot 类,这里就是 Scroll 类  
 4   let match = query(input);
if (match ==  6     throw  ParchmentError(`Unable to create ${input} blot`);
 7   }  
 8   let BlotClass = <BlotConstructor>match;  
 9   let node = input instanceof Node || input['nodeType'] === Node.TEXT_NODE
10     ? input
11     : BlotClass.create(value);
12 
13    最后返回 Scroll 对象
14   new BlotClass(<Node>node,value);
15 }
2   ql-cursor: ƒ Cursor(domNode,selection),1)">3   ql-editor: ƒ Scroll(domNode,config), 这个就是 Scroll 类
4   ql-formula: ƒ FormulaBlot(),1)">5   ql-syntax: ƒ SyntaxCodeBlock(),1)">6   ql-video: ƒ Video(),1)">7
1 <div class="ql-editor" contenteditable="true">
2   <p>
3     Hello
4     <strong>World</strong>
5   </p>
6   ...  其他编辑器内容
7 </div>
class Scroll extends ScrollBlot {
 2   constructor(domNode,config) {
 3     super(domNode);
 4     ...  
 5   }    
 6 
 7    标识批量更新的开始,此时执行 update / optimize 都不会进行实际的更新   
 8   batchStart() {
 9     this.batch = ;  
10 11 
12    标识批量更新的结束
13   batchEnd() {
14     15     .optimize();  
16 17 
18    在制定位置删除制定长度的内容  
19    比如:deleteAt(6,5) 将删除 "World"  
20    在 Quill 的 API 中对应 deleteText(index,source) 方法  
21   deleteAt(index,length) {}   
22  
23    设置编辑器的可编辑状态  
24   enable(enabled = 25     this.domNode.setAttribute('contenteditable'26 27 
28    在制定位置用制定格式格式化制定长度的内容  
29    比如:formatAt(6,5,'bold',false) 将取消 "World" 的粗体格式  
30    在 Quill 的 API 中对应 formatText(index,value,source) 方法 formatAt(index,format,value) {
31     this.whitelist != null && !this.whitelist[format]) 32     super.formatAt(index,value); 33 34 
 在制定位置插入内容  
 比如:insertAt(11,'\n你好,世界');  
37    在 Quill 的 API 中对应 insertText(index,text,source)  
38    Quill 中的 insertText 其实是 Scroll 的 insertAt 和 formatAt 的复合方法  
39   insertAt(index,def) {}    
40 
41    在某个 Blot 前面插入 Blot  
42   insertBefore(blot,ref) {}    
43 
44    弹出当前位置 Blot 路径最外面的叶子 Blot(会改变原数组)
45   leaf(index) { this.path(index).pop() || [null,-1];  }    
46 
47    实际上调用的是父类 ContainerBlot 的 descendant 方法  
48    目的是得到当前位置所在的 Blot 对象
49   line(index) {
50     if (index === .length()) {
51       this.line(index - 152     }
53     .descendant(isLine,index);
54 55 
56    获取某一范围的 Blot 对象  
57   lines(index = 0,length = Number.MAX_VALUE) {}    
58 
59    TODO
60   optimize(mutations = [],context = {}) {
61     this.batch === true) 62     super.optimize(mutations,context);
63     if (mutations.length > 064       .emitter.emit(Emitter.events.SCROLL_OPTIMIZE,mutations,1)">65     }  
66 67 
68    实际上调用的是父类 ContainerBlot 的 path 方法  
69    目的是得到当前位置的 Blot 路径,并排除 Scroll 自己  
70    Blot 路径就和 DOM 节点路径是对应的  
71    比如:DOM 节点路径 div.ql-editor -> p -> strong,  
72    对应 Blot 路径就是 [[Scroll div.ql-editor,0],[Block p,[Bold strong,6]]
73   path(index) {
74     return super.path(index).slice(1);  Exclude self  
75 76 
77   78   update(mutations) {
79     80 81   }
82 }
83 
84 Scroll.blotName = 'scroll'85 Scroll.className = 'ql-editor'86 Scroll.tagName = 'DIV'87 Scroll.defaultChild = 'block'88 Scroll.allowedChildren = [Block,BlockEmbed,Container];
89 
90 export default Scroll;

加入我们

原创声明
本站部分文章基于互联网的整理,我们会把真正“有用/优质”的文章整理提供给各位开发者。本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
本文链接:http://www.jiecseo.com/news/show_65536.html