使用Slab/Quill的Parchment模块克隆Medium编辑器体验
2025-07-05 01:50:28作者:秋泉律Samson
前言
在现代富文本编辑器的开发中,保持一致的编辑体验面临两大挑战:数据一致性和行为可预测性。DOM本身无法满足这两点要求,因此现代编辑器通常需要维护自己的文档模型来表示内容。在Slab/Quill项目中,Parchment模块就是这样的解决方案。
Parchment基础架构
Parchment是Quill的文档模型实现,它具有以下核心特点:
- 树形结构:与DOM类似,Parchment文档也是树形结构
- Blot节点:Parchment中的节点称为Blot,是对DOM节点的抽象
- 预定义Blot类型:包括Scroll、Block、Inline、Text和Break等基础类型
当用户输入时,Text blot会自动与对应的DOM Text节点同步,回车操作会创建新的Block blot。Parchment要求能够包含子节点的Blot必须至少有一个子节点,因此空的Block会被填充Break blot。
实现基本文本格式
加粗和斜体实现
要实现基本的文本格式如加粗和斜体,我们需要继承Inline基类:
const Inline = Quill.import('blots/inline');
class BoldBlot extends Inline {
static blotName = 'bold';
static tagName = 'strong';
}
class ItalicBlot extends Inline {
static blotName = 'italic';
static tagName = 'em';
}
Quill.register(BoldBlot);
Quill.register(ItalicBlot);
注册后,我们就可以使用Quill的完整API来操作这些格式:
quill.insertText(0, '测试文本', { bold: true });
quill.formatText(0, 4, 'italic', true);
链接实现
链接的实现比简单的布尔格式更复杂,因为需要存储URL信息:
class LinkBlot extends Inline {
static blotName = 'link';
static tagName = 'a';
static create(value) {
const node = super.create();
node.setAttribute('href', value);
node.setAttribute('target', '_blank');
return node;
}
static formats(node) {
return node.getAttribute('href');
}
}
块级元素实现
引用块和标题
块级元素的实现方式与行内元素类似,但继承的是Block基类:
const Block = Quill.import('blots/block');
class BlockquoteBlot extends Block {
static blotName = 'blockquote';
static tagName = 'blockquote';
}
class HeaderBlot extends Block {
static blotName = 'header';
static tagName = ['H1', 'H2'];
static formats(node) {
return HeaderBlot.tagName.indexOf(node.tagName) + 1;
}
}
分隔线实现
分隔线是我们的第一个叶子节点Blot实现:
const BlockEmbed = Quill.import('blots/block/embed');
class DividerBlot extends BlockEmbed {
static blotName = 'divider';
static tagName = 'hr';
}
插入分隔线时需要特别注意保持用户选择:
onClick('#divider-button', () => {
const range = quill.getSelection(true);
quill.insertText(range.index, '\n', Quill.sources.USER);
quill.insertEmbed(range.index + 1, 'divider', true, Quill.sources.USER);
quill.setSelection(range.index + 2, Quill.sources.SILENT);
});
多媒体内容实现
图片实现
图片使用对象作为值,可以存储更多元信息:
class ImageBlot extends BlockEmbed {
static blotName = 'image';
static tagName = 'img';
static create(value) {
const node = super.create();
node.setAttribute('alt', value.alt);
node.setAttribute('src', value.url);
return node;
}
static value(node) {
return {
alt: node.getAttribute('alt'),
url: node.getAttribute('src')
};
}
}
视频实现
视频的实现方式与图片类似:
class VideoBlot extends BlockEmbed {
static blotName = 'video';
static tagName = 'iframe';
static create(url) {
const node = super.create();
node.setAttribute('src', url);
node.setAttribute('frameborder', '0');
node.setAttribute('allowfullscreen', true);
return node;
}
static value(node) {
return node.getAttribute('src');
}
}
高级主题:自定义内容类型
通过Parchment的强大扩展能力,我们可以实现更复杂的内容类型,如嵌入式推文:
class TweetBlot extends BlockEmbed {
static blotName = 'tweet';
static tagName = 'div';
static create(id) {
const node = super.create();
node.dataset.id = id;
// 这里可以添加实际的推文嵌入逻辑
return node;
}
static value(node) {
return node.dataset.id;
}
}
总结
通过Parchment模块,Slab/Quill提供了强大的文档模型抽象能力,使开发者能够:
- 精确控制编辑器支持的内容类型
- 确保文档结构的规范性和一致性
- 实现复杂的富文本编辑功能
- 扩展编辑器支持的自定义内容类型
这种架构使得构建类似Medium的富文本编辑体验变得可行且可维护,同时保证了编辑体验的一致性和数据结构的规范性。