Tiptap 无头富文本编辑器指南:3 步跑通你的自定义编辑器
【免费下载链接】tiptapThe headless rich text editor framework for web artisans.项目地址: https://gitcode.com/GitHub_Trending/ti/tiptap
做内容类产品时,你可能遇到过这样的局面:评论框想加个加粗,文档编辑要标题、列表和引用,客户又提出把工具栏挪个位置。如果选的是 UI 和逻辑焊死的编辑器,每次改样式都得去抠它的内部代码。Tiptap 走的是另一条路——它是一个基于 ProseMirror 的无头富文本编辑器(headless rich text editor):核心只负责内容模型、状态和编辑逻辑,按钮、工具栏、皮肤全由你自己搭。
打个比方:Tiptap 像一台没有外壳的"编辑引擎",你负责给它装什么外壳,它负责把编辑这件事做对。这样换来的是"核心逻辑不变,界面千变万化",同一套扩展可以同时挂在 Vue 后台和 React 前台里。
项目速览:Tiptap 是什么、适合谁
| 一句话定位 | 核心特性清单 | 适用与不适用场景 |
|---|---|---|
| 无头、跨框架、靠扩展拼装能力的富文本编辑器 | 基于 ProseMirror(README 提到该库已用于 New York Times、The Guardian、Atlassian 等,见 packages/core/README.md);不含任何内置 UI;官方 + 社区共 100+ 扩展(README);Vue 2/3、React、原生 JS 三种接入方式;命令链 API;Yjs + Hocuspocus 开源协作;MIT 协议 | ✅ 需要深度定制编辑界面、多框架复用编辑核心、要做协同编辑的项目;❌ 只需要一个简单评论框、或想要开箱即用工具栏 UI 的场景 |
核心概念速览:3 个词看懂 Tiptap 的分层
- Extension(扩展):像手机的应用商店。加粗、链接、表格、撤销……每个功能都是一个独立扩展,编辑器就是"核心 + 一份扩展清单"。用不上的功能不注册即可,不存在"背着一堆用不上的代码"。
- Node 与 Mark(节点与标记):文档是一棵节点树。Node 是块级结构(段落、标题、代码块),Mark 是包在文字上的样式(加粗、斜体、行内代码)。理解这一对,就理解了 Tiptap 里 90% 的概念。
- 命令链(Command Chain):编辑操作像流水线一样串起来。
editor.chain().focus().toggleBold().run()读起来就是"聚焦,然后切换加粗,执行",比分别调用多个 API 再手动同步状态清爽得多。
快速上手:2 条命令装好,10 行代码出第一个编辑器 🚀
先安装核心依赖和框架适配器(以 Vue 3 为例,React 把最后一行换成@tiptap/react):
npm i @tiptap/core @tiptap/starter-kit @tiptap/vue-3StarterKit 是官方打包的基础扩展集,包含加粗、斜体、标题、列表、引用、撤销重做等(见 packages/starter-kit/src/starter-kit.ts)。最小可用配置:
import { useEditor, EditorContent } from '@tiptap/vue-3' import StarterKit from '@tiptap/starter-kit' const editor = useEditor({ extensions: [StarterKit], content: '<p>你好,Tiptap</p>', })模板里放一个<EditorContent :editor="editor" />,页面上就会出现一个可编辑的contenteditable区域,能打字、能换行。工具栏需要你自己写几个按钮,通过editor.chain().toggleBold().run()之类的命令去驱动。
建议在项目里这样组织目录,后面加自定义扩展不慌:
src/ └── editor/ ├── extensions/ # 自定义 Node / Mark / Extension ├── components/ # 工具栏等 UI 组件 └── useMyEditor.ts # 编辑器实例与扩展配置跑通后预期效果:编辑区可正常输入;editor.getHTML()/editor.getJSON()能拿到结构化内容;给 StarterKit 传configure({ codeBlock: false })即可按需关掉某个功能。
深入解析:3 个真正有价值的技术点 🔍
扩展机制:能力是"注册制"的
为什么重要:它决定了编辑器能不能"长"成业务需要的样子。原理一句话:所有扩展统一继承自 Extension 基类,可以贡献 schema 节点、命令、按键映射、粘贴规则,互不干扰。关键代码:自己加一个自定义命令只需几十行:
import { Extension } from '@tiptap/core' export const QuoteAction = Extension.create({ name: 'quoteAction', addCommands() { return { toggleQuote: () => ({ commands }) => commands.toggleNode('blockquote'), } }, })实际效果:任何组件、任何框架里都能editor.commands.toggleQuote(),功能调用和 UI 彻底解耦。
Node 定义:用一棵树描述你的文档结构
为什么重要:业务内容(病历、试卷、产品卡)往往不是"段落 + 标题"能表达的。原理一句话:Node.create()声明节点的名字、分组、可包含内容和属性,再给个renderHTML就能进 schema。关键代码:
import { Node } from '@tiptap/core' export const Figure = Node.create({ name: 'figure', group: 'block', content: 'image', addAttributes() { return { caption: { default: '' } } }, renderHTML({ HTMLAttributes }) { return ['figure', { ...HTMLAttributes, 'data-caption': HTMLAttributes.caption }, 0] }, })实际效果:编辑器从此"认识"图片说明块,粘贴进来自动解析,序列化出去结构稳定,前后端共享同一份 JSON。
命令链:把多步操作合成一条流水线
为什么重要:复杂操作(聚焦 → 选中 → 加粗 → 设色)最容易在状态同步上翻车。原理一句话:chain()把命令排成队列,前一步失败则后续自动跳过,run()时一次性派发事务。关键代码:
editor .chain() .focus('end') .toggleBold() .run()实际效果:工具栏、快捷键、AI 触发脚本可以共用同一套命令写法,不用再各自维护"当前选区是什么"。
避坑清单:4 个高频问题与解法 ⚠️
| 问题 | 原因 | 解法 |
|---|---|---|
| 编辑器空白、无法输入 | 没注册 Document / Paragraph / Text 这些基础节点(手写扩展清单时漏了) | 直接用 StarterKit,或手动确保这三个节点在列 |
React/Next.js 下报window is not defined | 编辑器依赖 DOM,SSR 阶段提前创建了实例 | React 端设置immediatelyRender: false,只在客户端构建(见 packages/react/src/useEditor.ts) |
| 从 Word/网页粘贴后样式混乱 | 粘贴的 HTML 携带大量 schema 之外的标签和属性 | 超出 schema 的内容解析时会被自动忽略;用扩展里的pasteRules做针对性清洗 |
| 大文档下保存接口被打爆 | onUpdate每次内容变更都会触发 | 监听事件后自行防抖(如 300ms)再提交editor.getHTML() |
性能与安全要点:只注册用得到的扩展,StarterKit 支持逐项关闭(codeBlock: false);历史栈长度可通过 undoRedo 配置控制;内容出前端后仍应在服务端再做一次净化,不要把编辑器 schema 当安全边界。
选型参考:Tiptap 和另外两条路怎么选
| 维度 | Tiptap | 手写 contenteditable | 商业编辑器(带现成工具栏) |
|---|---|---|---|
| 开发成本 | 中:核心现成,UI 自建 | 高:粘贴、选区、移动端光标都要自己兜 | 低:开箱即用 |
| 扩展性 | 高:100+ 扩展 + 自由定制节点/视图 | 完全自由但全要自己维护 | 受限于厂商插件生态 |
| 多框架复用 | Vue 2/3、React、原生 JS 同一套核心 | 天然无框架绑定,但代码难迁移 | 各框架版本能力常不一致 |
| 协同编辑 | 开源方案:Yjs + Hocuspocus 后端 | 全靠自己 | 多内置,但通常绑定其云服务 |
| 费用 | MIT 免费;部分高级 Pro 扩展需订阅(README) | 0 | 按授权收费 |
什么情况下不建议选它:如果只是加个简单评论框,textarea 就够了,引入 Tiptap 是杀鸡用牛刀;如果团队只有两个人、要求两三天上线且没有前端精力自建工具栏,无头意味着第一天没有现成 UI,上手成本会被放大(可以参考 demos/ 里的现成页面抄结构);如果深度依赖某商业编辑器的云服务和私有插件,迁移收益要重新算账。
资源与展望
两个值得跟进的问题:一是 AI 生成内容与实时编辑如何共存——仓库里已经出现 ai-toolkit(含流式显示 streaming-reveal 相关实现),后续"边生成边可编辑"的体验还会怎么演进?二是多模态内容(音频、数学公式、图表)越来越多,节点扩展的边界会扩展到哪?
- 核心源码:packages/core/src/
- 官方示例站源码(可按需 fork 改):demos/
- 入门教程示例:demos/src/Tutorials/
- 协作编辑示例:demos/src/Examples/CollaborativeEditing/
- 参与贡献:CONTRIBUTING.md
如果只想验证一下它值不值得进你的技术栈,最快的方式是:clone 仓库(git clone https://gitcode.com/GitHub_Trending/ti/tiptap),打开 demos 跑起来,挑一个最接近你业务的示例改两天——无头编辑器好不好用,两周之内就会有答案。
【免费下载链接】tiptapThe headless rich text editor framework for web artisans.项目地址: https://gitcode.com/GitHub_Trending/ti/tiptap
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考