CKEditor深度实践:从配置到插件开发的完整指南
为什么我要折腾 CKEditor 的不同方案?
最近一个项目要给后台加个富文本编辑器,需求不算复杂:支持基础格式、图片上传、代码块,最好还能自定义按钮。老板说“别搞太重”,但产品经理又想要“类似 Notion 的体验”。我翻了翻老项目,发现过去三年里我用过三种 CKEditor 方案:CKEditor 4(经典版)、CKEditor 5 Classic Build、CKEditor 5 自定义构建(Custom Build)。每次选型都踩过坑,这次干脆写篇对比,省得下次再纠结。
谁更灵活?谁更省事?
先说结论:如果只是简单用,直接上 CKEditor 5 Classic Build;如果要深度定制,必须自己搭 Custom Build;至于 CKEditor 4,能不用就别用了。
CKEditor 4 是我最早接触的版本,配置简单,文档齐全,但它的架构是基于 DOM 操作的,和现代前端框架(比如 React、Vue)集成起来特别别扭。而且官方已经停止功能更新,只修安全漏洞。我去年在一个老项目里被迫用它,结果为了兼容 Vue 3 的响应式数据,硬是写了两套状态同步逻辑,头疼。
CKEditor 5 Classic Build 就舒服多了。它开箱即用,几行代码就能跑起来,API 也清爽。我比较喜欢它的“模型-视图”分离架构,和框架集成天然友好。但问题在于——你只能用它打包好的功能。比如我想加个“插入公式”按钮?没门,除非你愿意接受它臃肿的 Math 插件(连 LaTeX 渲染库都给你塞进来了)。
所以,当需求稍微复杂一点,比如要限制用户只能用特定标签、或者按钮要按业务逻辑动态显示,我就得祭出 CKEditor 5 Custom Build。这玩意儿本质上是个 CLI 工具,让你从零组装插件。虽然前期要花时间配 webpack(或者 vite),但换来的是完全掌控权。亲测有效,上周我刚用它砍掉了 60% 的无用代码,首屏加载快了近 1 秒。
核心代码就这几行,但坑不少
先看最简单的 Classic Build 用法:
import { ClassicEditor } from '@ckeditor/ckeditor5-build-classic';
ClassicEditor
.create(document.querySelector('#editor'), {
toolbar: ['bold', 'italic', 'link', 'bulletedList', 'numberedList'],
image: {
toolbar: ['imageTextAlternative']
}
})
.catch(error => {
console.error(error);
});
看起来很美好,对吧?但这里有个大坑:如果你在 SPA(比如 Vue 或 React)里动态创建/销毁编辑器,必须手动调用 destroy() 方法,否则内存泄漏到你怀疑人生。我第一次用 React 时,切换路由后编辑器残留的事件监听器直接让页面卡成幻灯片。后来才在 GitHub issue 里翻到这个提醒。
再看 Custom Build,初始化代码其实差不多,但构建过程才是重点。你得先用官方脚手架生成项目:
npm init ckeditor5 --custom my-custom-editor
cd my-custom-editor
npm install
然后在 src/ckeditor.js 里按需引入插件:
import { ClassicEditor } from '@ckeditor/ckeditor5-editor-classic';
import { Essentials } from '@ckeditor/ckeditor5-essentials';
import { Bold, Italic } from '@ckeditor/ckeditor5-basic-styles';
import { Link } from '@ckeditor/ckeditor5-link';
import { Paragraph } from '@ckeditor/ckeditor5-paragraph';
// 只引入你需要的,比如不要 Heading
// import { Heading } from '@ckeditor/ckeditor5-heading';
export default class MyCustomEditor extends ClassicEditor {}
MyCustomEditor.builtinPlugins = [
Essentials,
Bold,
Italic,
Link,
Paragraph
];
MyCustomEditor.defaultConfig = {
toolbar: ['bold', 'italic', 'link'],
language: 'zh-cn'
};
最后构建:
npm run build
生成的 build/ckeditor.js 就是你专属的轻量版编辑器。这里注意我踩过好几次坑:别在 defaultConfig 里写太复杂的配置,有些插件依赖会漏掉。比如你用了 Image 插件但没显式引入 ImageToolbar,工具栏就出不来。官方文档写得有点绕,建议直接看插件源码里的 pluginName 和依赖声明。
我的选型逻辑
现在我基本按这个流程决策:
- 需求简单、工期紧:直接上 CKEditor 5 Classic Build,最多通过
toolbar配置删减按钮。省下的时间够我多喝两杯咖啡。 - 需要深度定制或性能敏感:毫不犹豫 Custom Build。虽然前期多花半天配环境,但后期维护和加载速度优势巨大。特别是当你要集成第三方服务(比如从 jztheme.com/api/fetch-image 拉图)时,自定义上传适配器写起来特别顺手。
- 维护老系统:如果还在用 CKEditor 4,趁重构机会换掉。它的 jQuery 依赖和全局污染在现代工程里就是定时炸弹。
另外提一嘴,CKEditor 5 的 Balloon Editor、Inline Editor 等变体其实底层都一样,只是 UI 不同。我试过 Balloon,适合注释类场景,但普通内容编辑还是 Classic 最稳。别被花哨的 demo 迷了眼,实际用起来发现光标定位和移动端兼容性反而不如 Classic。
踩坑提醒:这三点一定注意
1. 图片上传别信默认配置。CKEditor 5 默认把图片转成 base64 嵌在 HTML 里,大图直接撑爆数据库。我改成了上传到服务器,返回 URL:
// 在 Custom Build 的配置里
image: {
upload: {
adapter: (loader) => {
return {
upload: () => {
return new Promise((resolve, reject) => {
const formData = new FormData();
formData.append('upload', loader.file);
fetch('https://jztheme.com/api/upload', {
method: 'POST',
body: formData
})
.then(res => res.json())
.then(data => {
if (data.url) {
resolve({ default: data.url });
} else {
reject(data.error);
}
})
.catch(reject);
});
}
};
}
}
}
2. 中文输入法下光标错位。这是 CKEditor 5 早期版本的通病,升级到 35+ 基本解决,但如果你用旧版,记得在 config 里加 typing: { transformations: { remove: [] } } 临时缓解。
3. 别在 SSR 里直接渲染。CKEditor 5 依赖浏览器 API,服务端渲染会报错。我在 Next.js 里用 dynamic import + { ssr: false } 绕过,虽然首屏没编辑器,但总比白屏强。
总结一下
CKEditor 5 Custom Build 是我现在的首选,尽管它需要多写点配置,但换来的是干净的 bundle 和自由的扩展能力。Classic Build 适合快速交付,但别指望它能优雅地满足复杂需求。至于 CKEditor 4,让它留在历史里吧。
以上是我踩坑后的总结,希望对你有帮助。有更优的实现方式欢迎评论区交流,比如你们怎么处理多语言内容的双向绑定?我还在找更优雅的方案。

暂无评论