canvas-editor 快速上手:基于 Canvas 的 WYSIWYG 富文本编辑器集成指南
canvas-editor 快速上手:基于 Canvas 的 WYSIWYG 富文本编辑器集成指南
本文是 canvas-editor(
@hufe921/canvas-editor)官方文档 Getting Started 的深度实战解读。canvas-editor 是一款基于 Canvas/SVG 自研光标与文本排版内核的 WYSIWYG(所见即所得)富文本编辑器。读完本文,你将掌握从 NPM 安装、容器准备、实例化编辑器(正文/页眉/页脚数据格式)、到配置编辑器与后续扩展(命令 API、右键菜单、插件、国际化)的完整落地路径,可直接照抄示例在你的业务项目中跑通一个可编辑文档。
编辑器概览与能力清单
canvas-editor 的核心卖点是完整自研的光标与文本排版实现,不依赖浏览器原生 contenteditable,因此在不同浏览器环境下的排版表现更可控。底层渲染默认基于 Canvas,同时官方还提供了两个可切换的渲染分支:
- SVG 渲染分支:见
feature/svg分支代码; - PDF 渲染分支:基于 pdfjs 完整绘制 PDF,见
feature/pdf分支代码。
⚠️ 官方只发布编辑器核心层的 npm 包,菜单栏、工具栏等外部工具需要参考文档自行扩展,或直接参考官方仓库的实现,详见官方 demo。
从当前仓库源码看,编辑器实例对外暴露了 command(命令系统)、listener(监听器)、eventBus(事件总线)、override(方法重写)、register(右键菜单/快捷键/i18n 注册)、macro(宏管理)、use(插件注册)等能力,见 src/editor/index.ts。
核心特性一览
根据官方文档与 features 文档,编辑器提供如下能力(数字为当前仓库文档口径):
| 分类 | 能力说明 |
|---|---|
| 富文本操作 | 撤销/重做、格式刷、清除格式、字体、字号、加粗、斜体、下划线、删除线、上标/下标、字体颜色、高亮、对齐、行高、行距、标题、列表、首行缩进 |
| 插入元素 | 表格、图片、超链接、分割线、分页符、LaTeX 公式、日期选择器、Tab、内容块(iframe/video)、标签、区域 |
| 表格 | 插入/删除行列、合并/拆分单元格、边框样式、背景色、垂直对齐、rowspan/colspan、跨页分页(pagingRepeat 表头回显) |
| 打印 | 基于 canvas 转图片、PDF 绘制 |
| 控件(6 种) | 文本、数值、下拉(Select)、单选组、复选组、日期;支持占位符、前后缀、默认值、禁用、缩进对齐规则、控件嵌套 |
| 级联表达式 | 控件值变化时自动联动其他控件/标题的可见性、必填、可编辑、可删除状态,支持计算表达式(compute) |
| 修订留痕(Trace) | 记录并可视化标记插入(蓝色下划线)与删除(红色中划线)的修订 |
| 宏(Macro) | 录制宏与脚本宏,支持命令录制/回放、JSON 序列化、自定义逻辑 |
| 编辑器模式(8 种) | Edit(编辑)、Clean(清洁)、Readonly(只读)、Form(表单)、Print(打印)、Design(设计)、Graffiti(涂鸦)、Trace(留痕) |
| 页面模式 | 分页(paging)/连续(continuity),支持页边距、页眉、页脚、页码 |
| 水印 | 文字/图片水印,可置于内容上方或下方 |
| 评论 | 添加、定位、删除文本评论 |
| 目录 | 根据标题自动生成,点击跳转 |
| 右键菜单 | 内置菜单,可按元素类型自定义 |
| 快捷键 | 内置快捷键,支持自定义覆盖与禁用 |
| 拖拽 | 文本、元素、控件 |
| 搜索替换 | 关键词搜索、导航、替换 |
| i18n | 内置中英文,可扩展其他语言 |
| 插件生态 | 官方提供 20+ 插件(Word/Excel 导入导出、公式、评论、拼写检查等) |
仓库文档统计口径为:19 种元素类型、6 种表单控件、8 种编辑器模式、154+ 命令 API、16+ 事件、20+ 官方插件。
Step 1:安装 NPM 包
在项目中安装核心包:
npm i @hufe921/canvas-editor --save
包名与当前仓库 package.json 中声明的 name: "@hufe921/canvas-editor" 一致,当前仓库版本为 1.0.4,类型声明位于 dist/src/editor/index.d.ts,同时提供 UMD(dist/canvas-editor.umd.cjs)与 ESM(dist/canvas-editor.js)两种产物,可直接在浏览器 <script> 或打包工具中使用。
需要说明的是,当前仓库源码要求的 Node 版本为 >=24.13.1(见 package.json 的 engines 字段),这是构建仓库源码的环境前提;作为 npm 依赖使用时,请以你实际项目环境为准。
Step 2:准备容器 DOM
编辑器会挂载到你指定的 DOM 容器上:
<div class="canvas-editor"></div>
该容器最终由 Draw 类接管并注入编辑器的内部 DOM 结构,见 src/editor/index.ts。容器可以是一个普通 div,编辑器会自动为其构建页面渲染区。
Step 3:实例化编辑器
只含正文的示例
import Editor from '@hufe921/canvas-editor'
new Editor(
document.querySelector('.canvas-editor'),
[
{
value: 'Hello World'
}
],
{}
)
构造函数签名为 new Editor(container, data, options),见 src/editor/index.ts:
container:挂载容器(HTMLDivElement);data:可以是IElement[](仅正文)或IEditorData(含页眉/正文/页脚/涂鸦);options:IEditorOption配置对象,可省略(默认{})。
从源码看,当 data 为数组时,会直接作为正文元素列表(mainElementList),并统一经过 formatElementList 做元素格式化与补偿(src/editor/index.ts)。每个元素的最简形态就是一个包含 value 字符串的对象,对应接口 IElementBasic(src/editor/interface/Element.ts),元素可选字段还包括 type(元素类型)、id、extension、externalId、hint 等。
含正文、页眉、页脚的示例
import Editor from '@hufe921/canvas-editor'
new Editor(
document.querySelector('.canvas-editor'),
{
header: [
{
value: 'Header',
rowFlex: RowFlex.CENTER
}
],
main: [
{
value: 'Hello World'
}
],
footer: [
{
value: 'canvas-editor',
size: 12
}
]
},
{}
)
这个示例对应 IEditorData 接口(src/editor/interface/Editor.ts):
interface IEditorData {
header?: IElement[] // 页眉元素
main: IElement[] // 正文元素(必填)
footer?: IElement[] // 页脚元素
graffiti?: IGraffitiData[] // 涂鸦数据
}
rowFlex 使用编辑器导出的枚举 RowFlex(左对齐/居中/右对齐/两端对齐/分散对齐),源码定义见 src/editor/dataset/enum/Row.ts:
export enum RowFlex {
LEFT = 'left',
CENTER = 'center',
RIGHT = 'right',
ALIGNMENT = 'alignment',
JUSTIFY = 'justify'
}
与官方 demo(src/main.ts)中“第一人民医院 / 门诊病历”的页眉示例完全一致——页眉元素支持 size(字号)、rowFlex(行对齐)、type: ElementType.SEPARATOR(插入分割线)等字段,见 src/main.ts。
实例上的可用能力
new Editor(...) 返回的实例除渲染文档外,还挂载了以下核心模块(src/editor/index.ts):
instance.command:命令系统,所有execute*API(如executeUndo、executeBold、executeInsertTable、executePrint等);instance.listener:单回调监听;instance.eventBus:发布/订阅事件总线;instance.override:拦截/重写粘贴、复制、拖拽等默认行为;instance.register:注册自定义右键菜单、快捷键与语言包;instance.macro:宏管理;instance.use(plugin):注册插件;instance.version:版本号;instance.destroy():销毁编辑器。
官方 demo 中通过 Reflect.set(window, 'editor', instance) 与 Reflect.set(window, '__CANVAS_EDITOR_INSTANCE__', instance) 将实例挂到全局,分别供 Cypress 测试与 devtools 调试面板使用(src/main.ts)。
Step 4:配置编辑器
第三步中的第三个参数就是配置对象 IEditorOption。官方文档指明“详细配置见下一章节”,即 option.md,这里给出高频配置项的速查(默认值取自文档与 IEditorOption 接口 src/editor/interface/Editor.ts):
interface IEditorOption {
mode?: EditorMode // 编辑模式:Edit / Clean / Readonly / Form / Print / Design / Graffiti / Trace。默认: Edit
locale?: string // 语言。默认: zhCN
defaultType?: string // 默认元素类型。默认: TEXT
defaultColor?: string // 默认字体颜色。默认: #000000
defaultFont?: string // 默认字体。默认: Microsoft YaHei
defaultSize?: number // 默认字号。默认: 16
minSize?: number // 最小字号。默认: 5
maxSize?: number // 最大字号。默认: 72
defaultBasicRowMarginHeight?: number // 默认行高。默认: 8
defaultRowMargin?: number // 默认行距。默认: 1
defaultTabWidth?: number // 默认 Tab 宽度。默认: 32
width?: number // 纸张宽度。默认: 794
height?: number // 纸张高度。默认: 1123
scale?: number // 缩放比例。默认: 1
pageGap?: number // 纸张间距。默认: 20
margins?: IMargin // 页边距 [top, right, bottom, left]。默认: [100, 120, 100, 120]
pageMode?: PageMode // 页面模式:Paging(分页)/ Continuity(连续)。默认: Paging
renderMode?: RenderMode // 渲染模式:speed(多字组合渲染)/ compatibility(逐字渲染,规避浏览器字体等环境差异)。默认: speed
historyMaxRecordCount?: number // 撤销/重做历史最大记录数。默认: 100
printPixelRatio?: number // 打印像素比(越大越清晰、体积越大)。默认: 3
watermark?: IWatermark // 水印配置
control?: IControlOption // 控件配置
placeholder?: IPlaceholder // 占位符配置
// ... 更多见 option.md
}
mode 的可选值在源码枚举 EditorMode 中有完整定义(src/editor/dataset/enum/Editor.ts):edit(可编辑、辅助元素均存在)、clean(隐藏分页符等辅助元素)、readonly(不可编辑)、form(仅控件内可编辑)、print(隐藏辅助元素、选区、未书写控件及边框)、design(不处理不可删除、只读等配置)、graffiti(屏蔽选区、长按绘制线条)、trace(只读查看痕迹)。
一个带配置的完整实例化示例:
import Editor, { EditorMode, PageMode } from '@hufe921/canvas-editor'
const instance = new Editor(
document.querySelector('.canvas-editor'),
[
{ value: 'Hello World' }
],
{
mode: EditorMode.EDIT,
locale: 'en',
defaultFont: 'Arial',
defaultSize: 14,
pageMode: PageMode.PAGING,
historyMaxRecordCount: 200,
watermark: {
data: 'DRAFT',
color: '#AEB5C0',
opacity: 0.3,
repeat: true
},
placeholder: {
data: '请输入内容...'
}
}
)
进一步扩展:实例 API、命令与插件
实例化完成只是开始。官方文档体系建议按以下顺序继续深入:
- 实例 API:
instance.version、instance.destroy()等,见 api-instance.md。注意destroy()只销毁编辑器 DOM 与相关事件,菜单栏、工具栏、外部变量等需要自行清理。 - 执行类命令:
instance.command.executeXxx(),完整列表(154+)见 command-execute.md;获取类命令见 command-get.md。 - 完整配置:所有配置项(表格、页眉页脚、页码、控件、光标、标题、背景、分栏、留痕、标尺、放大镜等)见 option.md,实验性配置(如
lab.incrementalCompute行级增量布局)见 lab.md。 - 事件监听:
listener与eventBus(contentChange、rangeChange、rangeStyleChange、controlChange等 16+ 事件)见 listener.md 与 eventbus.md。 - 右键菜单与快捷键:内置菜单与自定义覆盖见 contextmenu-internal.md、contextmenu-custom.md;快捷键见 shortcut-internal.md、shortcut-custom.md。
- i18n:内置中文与英文,通过
register.langMap(locale, lang)扩展其他语言,见 i18n.md。 - 插件机制:
instance.use(plugin)注册插件,官方 20+ 插件(Word/Excel 导入导出、公式、评论、拼写检查等),自研插件开发见 plugin-custom.md,仓库内的示例插件位于 src/plugins(copy、markdown、spellcheck)。
从源码结构看(src/editor/index.ts),实例化时会依次构建:Command(基于 CommandAdapt 封装 Draw)→ MacroManager → ContextMenu → Shortcut → Register(聚合右键菜单、快捷键、i18n 注册入口,见 src/editor/core/register/Register.ts)→ Plugin(提供 use 注册)。这些模块共同构成了“核心层只做排版渲染、外部能力全部可扩展”的架构。
在仓库中运行与验证
如果你希望直接在当前仓库中体验完整编辑器(包含官方 demo 的菜单栏与病历模板数据 src/mock.ts),可以使用以下命令(环境需满足 node >= 24.13.1):
# 安装依赖
pnpm install
# 启动开发服务器(vite)
npm run dev
# 运行单元测试(vitest)
npm run test:unit
# 运行 E2E 测试(cypress,含编辑、菜单、控件、表格等场景)
npm run cypress:run
# 构建仓库自带的演示应用
npm run build
对应脚本定义见 package.json。仓库的单元测试覆盖了编辑器核心(tests/editor/editor.test.ts)、命令系统(tests/core/command)、事件总线、历史记录、插件、级联表达式等;测试工厂 tests/factories/editor.ts 中通过 new Editor(container, data, options) 创建实例,与本文第三步的用法完全一致。