在這之前,先來介紹一下 ProseMirror:
1. ProseMirror 是底層內核
-
定位:一個強大的 富文本編輯框架/引擎,不是一個成品編輯器。
-
作者:Marijn Haverbeke(CodeMirror 作者)。
-
核心思想:
- 用 schema(模式) 定義文檔結構(節點、marks、attributes)。
- 每個操作(加粗、插入段落)都是 transaction(事務)。
- 提供 collab(協作)、history、markdown 解析、插件系統 等能力。
-
問題:API 很底層、復雜(寫一個“加粗按鈕”就需要懂 state/transaction/command)。
2. Tiptap 是 ProseMirror 的高級封裝
-
定位:一個 基于 ProseMirror 的現代編輯器框架。
-
特點:
- 提供了更友好的 API(例如
editor.chain().focus().toggleBold().run()
,不用直接操作 transaction)。 - 內置常用擴展(StarterKit:paragraph、heading、bold、italic、list、code 等)。
- 框架無關(React、Vue、Svelte、純 JS 都能用)。
- 社區活躍,擴展豐富(Mention、SlashCommand、Table、Collaboration 等)。
- 專門為現代 Web 應用優化(支持移動端、協同編輯、富 UI 集成)。
- 提供了更友好的 API(例如
3. 核心區別
對比點 | ProseMirror | Tiptap |
---|---|---|
定位 | 底層引擎 / 框架 | 高級封裝 / 開發者友好的框架 |
難度 | 高(API 底層,文檔晦澀) | 中等(鏈式調用,擴展豐富) |
功能 | 一切都要自己實現 | 內置 StarterKit,常用功能即插即用 |
UI | 沒有(自己寫) | 沒有默認 UI,但社區有現成擴展 |
靈活性 | 無限靈活 | 靈活 + 更高生產力 |
適合人群 | 想深度定制、寫自己編輯器內核的人 | 想快速落地 Notion/Google Docs 類應用的人 |
4. 關系總結
- ProseMirror = 編輯器的內核(底層引擎)。
- Tiptap = 基于 ProseMirror 的開發框架(更好用的外殼)。
- 你用 Tiptap,其實是在用 ProseMirror,只不過被封裝了一層,更易上手。
- 如果你遇到 Tiptap 沒提供的功能,最終可能需要寫 ProseMirror 插件/擴展。
- ProseMirror vs Tiptap 的架構圖:
下面,回歸正題:
一、本文將對比 Tiptap Editor 和 TinyMCE,從技術架構、功能、擴展性、適用場景等方面做出分析。
1. 技術架構
-
Tiptap Editor
- 基于 ProseMirror,是一個現代化的富文本編輯框架。
- 完全用 JavaScript/TypeScript 編寫,UI 無關(React、Vue、Svelte 都能集成)。
- 更偏向“編輯器框架”,需要開發者配置和擴展。
-
TinyMCE
- 歷史悠久的傳統富文本編輯器(WYSIWYG),早期就用于網頁中的文字編輯。
- 內置 UI(工具欄、菜單)和功能較多,開箱即用。
- 偏向于“現成的產品”,即插即用。
2. 功能特性
-
Tiptap
- 靈活度高,可以完全自定義編輯體驗(例如 Notion、Slack、Linear 都是基于 ProseMirror/Tiptap)。
- 支持協同編輯(結合 yjs/y-websocket)。
- 插件體系強大(markdown、mention、slash command、自定義節點)。
- 原生支持移動端、現代前端框架。
- UI/菜單需要自己實現或用社區擴展。
-
TinyMCE
- 開箱即用的傳統功能:字體、顏色、表格、圖片上傳、對齊、列表等等。
- 插件體系完善,但更多是 WYSIWYG 的擴展(如 word count、spellcheck)。
- 有商業版(帶更多企業功能,如 MS Word/Google Docs 級別的協作和導出)。
- 移動端體驗不如 Tiptap,但兼容性很好。
3. 學習曲線
- Tiptap:需要較強的前端開發能力,理解 ProseMirror schema、node/mark 才能發揮最大價值。
- TinyMCE:學習成本低,只要引入腳本即可用,配置主要是工具欄和插件。
4. 可擴展性 & 自定義
-
Tiptap
- 高度可定制,可以實現類似 Notion、Coda、Obsidian 那種 block-based 編輯器。
- 自定義節點(自定義組件、Vue/React 元素嵌入)非常靈活。
- 對現代 web app(SaaS、在線文檔)很適合。
-
TinyMCE
- 自定義能力有限,雖然可以寫插件,但核心思想還是“富文本編輯器”。
- 適合標準化的富文本場景(CMS、論壇、評論系統)。
5. 使用場景
-
選擇 Tiptap
- 你在做一個 現代 SaaS/協作工具(比如 Notion、文檔協作、知識庫)。
- 需要 協作編輯、自定義 block/組件。
- 前端團隊實力較強,可以投入時間定制。
-
選擇 TinyMCE
- 你在做 CMS、表單、企業后臺,只需要 標準富文本(發文章、加粗、插入表格圖片)。
- 團隊希望 快速上線,不想花時間定制編輯器。
- 用戶群體對“Word 類似體驗”有需求。
6. 對比總結
特性 | Tiptap Editor (ProseMirror) | TinyMCE |
---|---|---|
定位 | 編輯器框架 (現代、靈活) | 成品富文本編輯器 (傳統、開箱即用) |
UI/工具欄 | 需要自己做 / 社區擴展 | 內置完善工具欄 |
功能擴展 | 無限靈活,可嵌入 React/Vue 組件 | 主要是 WYSIWYG 擴展 |
協同編輯 | 支持 (yjs 集成) | 商業版支持 |
移動端體驗 | 優秀 | 一般 |
學習成本 | 高(要理解 ProseMirror) | 低(配置工具欄即可) |
最佳適用場景 | Notion/知識庫/協作編輯 | CMS/后臺文章/傳統富文本 |
二、 下面將提供 Tiptap (Vue 3) 和 TinyMCE (純 HTML/JS) 的最小可運行示例
1. Tiptap (Vue 3 示例)
<template><div><EditorContent :editor="editor" class="border p-3 min-h-[200px]" /></div>
</template><script setup>
import { ref, onBeforeUnmount } from "vue"
import { EditorContent, useEditor } from "@tiptap/vue-3"
import StarterKit from "@tiptap/starter-kit"const editor = useEditor({extensions: [StarterKit],content: "<p>Hello <b>Tiptap</b> 🚀</p>",
})onBeforeUnmount(() => {editor?.destroy()
})
</script><style>
.ProseMirror {min-height: 200px;outline: none;
}
</style>
👉 特點:
- 這是最小示例,只帶 StarterKit(加粗、斜體、標題、列表、代碼等基礎功能)。
- 你可以往里加插件,比如 mention、slash command、表格、Markdown。
- 沒有默認工具欄,要自己寫按鈕控制(高度可定制)。
2. TinyMCE (純 HTML/JS 示例)
<!DOCTYPE html>
<html>
<head><script src="https://cdn.tiny.cloud/1/no-api-key/tinymce/6/tinymce.min.js"></script>
</head>
<body><textarea id="editor">Hello <b>TinyMCE</b> ?</textarea><script>tinymce.init({selector: '#editor',height: 300,plugins: 'lists link image table code',toolbar: 'undo redo | bold italic underline | bullist numlist | link image table | code',});</script>
</body>
</html>
👉 特點:
- 一引入就能用,帶完整工具欄。
- 插件和配置非常像傳統富文本(Word)。
- 如果要集成到 Vue/React,也有官方包,但核心就是
tinymce.init
。
📌 總結:
- Tiptap → 需要開發者寫 UI,但能做出 Notion/Slack/協作文檔 的感覺。
- TinyMCE → 一步到位,像 Word 編輯器,但定制自由度沒那么高。
三、下面示例將完善一下工具欄
Tiptap (Vue 3 + 工具欄 示例)
<template><div class="editor"><!-- 工具欄 --><div class="toolbar"><button @click="toggleBold" :class="{ active: editor.isActive('bold') }">B</button><button @click="toggleItalic" :class="{ active: editor.isActive('italic') }">I</button><button @click="setHeading(1)" :class="{ active: editor.isActive('heading', { level: 1 }) }">H1</button><button @click="setHeading(2)" :class="{ active: editor.isActive('heading', { level: 2 }) }">H2</button><button @click="toggleBulletList" :class="{ active: editor.isActive('bulletList') }">? List</button><button @click="toggleOrderedList" :class="{ active: editor.isActive('orderedList') }">1. List</button><button @click="toggleCodeBlock" :class="{ active: editor.isActive('codeBlock') }">Code</button></div><!-- 編輯區 --><EditorContent :editor="editor" class="editor-content" /></div>
</template><script setup>
import { onBeforeUnmount } from "vue"
import { EditorContent, useEditor } from "@tiptap/vue-3"
import StarterKit from "@tiptap/starter-kit"const editor = useEditor({extensions: [StarterKit],content: "<p>Hello <b>Tiptap</b> with Toolbar 🚀</p>",
})onBeforeUnmount(() => {editor?.destroy()
})// 工具欄方法
const toggleBold = () => editor.chain().focus().toggleBold().run()
const toggleItalic = () => editor.chain().focus().toggleItalic().run()
const setHeading = (level) => editor.chain().focus().toggleHeading({ level }).run()
const toggleBulletList = () => editor.chain().focus().toggleBulletList().run()
const toggleOrderedList = () => editor.chain().focus().toggleOrderedList().run()
const toggleCodeBlock = () => editor.chain().focus().toggleCodeBlock().run()
</script><style>
.editor {border: 1px solid #ccc;border-radius: 6px;padding: 8px;max-width: 600px;margin: auto;
}.toolbar {border-bottom: 1px solid #ddd;padding-bottom: 6px;margin-bottom: 6px;
}.toolbar button {margin-right: 6px;padding: 4px 8px;border: 1px solid #ccc;background: white;cursor: pointer;border-radius: 4px;
}.toolbar button.active {background: #007bff;color: white;
}.editor-content {min-height: 200px;padding: 6px;outline: none;
}
</style>
👉 這樣效果就是:
- 上面一排按鈕(加粗、斜體、H1、H2、列表、代碼塊)。
- 點擊按鈕就能直接控制編輯區。
- 你可以繼續擴展,比如 插入圖片、mention、slash command,靈活度很高。
- React + Tiptap + 工具欄示例,方便和 Vue 版本對比。
Tiptap (React + 工具欄 示例)
import React, { useEffect } from "react"
import { EditorContent, useEditor } from "@tiptap/react"
import StarterKit from "@tiptap/starter-kit"
import "./editor.css" // 樣式寫在單獨的 css 文件const TiptapEditor = () => {const editor = useEditor({extensions: [StarterKit],content: "<p>Hello <b>Tiptap</b> with Toolbar 🚀</p>",})useEffect(() => {return () => editor?.destroy()}, [editor])if (!editor) return nullreturn (<div className="editor">{/* 工具欄 */}<div className="toolbar"><button onClick={() => editor.chain().focus().toggleBold().run()}className={editor.isActive("bold") ? "active" : ""}>B</button><button onClick={() => editor.chain().focus().toggleItalic().run()}className={editor.isActive("italic") ? "active" : ""}>I</button><button onClick={() => editor.chain().focus().toggleHeading({ level: 1 }).run()}className={editor.isActive("heading", { level: 1 }) ? "active" : ""}>H1</button><button onClick={() => editor.chain().focus().toggleHeading({ level: 2 }).run()}className={editor.isActive("heading", { level: 2 }) ? "active" : ""}>H2</button><button onClick={() => editor.chain().focus().toggleBulletList().run()}className={editor.isActive("bulletList") ? "active" : ""}>? List</button><button onClick={() => editor.chain().focus().toggleOrderedList().run()}className={editor.isActive("orderedList") ? "active" : ""}>1. List</button><button onClick={() => editor.chain().focus().toggleCodeBlock().run()}className={editor.isActive("codeBlock") ? "active" : ""}>Code</button></div>{/* 編輯區 */}<EditorContent editor={editor} className="editor-content" /></div>)
}export default TiptapEditor
樣式 (editor.css)
.editor {border: 1px solid #ccc;border-radius: 6px;padding: 8px;max-width: 600px;margin: auto;
}.toolbar {border-bottom: 1px solid #ddd;padding-bottom: 6px;margin-bottom: 6px;
}.toolbar button {margin-right: 6px;padding: 4px 8px;border: 1px solid #ccc;background: white;cursor: pointer;border-radius: 4px;
}.toolbar button.active {background: #007bff;color: white;
}.editor-content {min-height: 200px;padding: 6px;outline: none;
}
👉 這樣你就有了:
- Vue 版 Tiptap + 工具欄
- React 版 Tiptap + 工具欄
- TinyMCE 對比版
📌 對比下來:
- TinyMCE:自帶工具欄,不需要寫 UI。
- Tiptap:工具欄要自己寫,但能完全定制,甚至做出 Notion 風格。
-
繼續,為 Tiptap 擴展一個「插入圖片」功能,這樣你能直接對比 TinyMCE 的圖片功能。
給出 React 示例(Vue 邏輯差不多,換
@tiptap/vue-3
即可)。
- 補充一個圖片上傳功能
React + Tiptap + 工具欄 + 插入圖片
import React, { useEffect } from "react"
import { EditorContent, useEditor } from "@tiptap/react"
import StarterKit from "@tiptap/starter-kit"
import Image from "@tiptap/extension-image"
import "./editor.css"const TiptapEditor = () => {const editor = useEditor({extensions: [StarterKit,Image, // 啟用圖片擴展],content: "<p>Hello <b>Tiptap</b> with Image 🚀</p>",})useEffect(() => {return () => editor?.destroy()}, [editor])if (!editor) return null// 插入圖片const addImage = () => {const url = window.prompt("請輸入圖片地址")if (url) {editor.chain().focus().setImage({ src: url }).run()}}return (<div className="editor">{/* 工具欄 */}<div className="toolbar"><button onClick={() => editor.chain().focus().toggleBold().run()}className={editor.isActive("bold") ? "active" : ""}>B</button><button onClick={() => editor.chain().focus().toggleItalic().run()}className={editor.isActive("italic") ? "active" : ""}>I</button><button onClick={() => editor.chain().focus().toggleHeading({ level: 1 }).run()}className={editor.isActive("heading", { level: 1 }) ? "active" : ""}>H1</button><button onClick={() => editor.chain().focus().toggleHeading({ level: 2 }).run()}className={editor.isActive("heading", { level: 2 }) ? "active" : ""}>H2</button><button onClick={() => editor.chain().focus().toggleBulletList().run()}className={editor.isActive("bulletList") ? "active" : ""}>? List</button><button onClick={() => editor.chain().focus().toggleOrderedList().run()}className={editor.isActive("orderedList") ? "active" : ""}>1. List</button><button onClick={() => editor.chain().focus().toggleCodeBlock().run()}className={editor.isActive("codeBlock") ? "active" : ""}>Code</button><button onClick={addImage}>🖼 插入圖片</button></div>{/* 編輯區 */}<EditorContent editor={editor} className="editor-content" /></div>)
}export default TiptapEditor
樣式 (editor.css)
.editor {border: 1px solid #ccc;border-radius: 6px;padding: 8px;max-width: 600px;margin: auto;
}.toolbar {border-bottom: 1px solid #ddd;padding-bottom: 6px;margin-bottom: 6px;
}.toolbar button {margin-right: 6px;padding: 4px 8px;border: 1px solid #ccc;background: white;cursor: pointer;border-radius: 4px;
}.toolbar button.active {background: #007bff;color: white;
}.editor-content {min-height: 200px;padding: 6px;outline: none;
}.editor-content img {max-width: 100%;height: auto;border-radius: 4px;margin: 8px 0;
}
👉 效果:
- 點擊「🖼 插入圖片」會彈出一個輸入框,輸入 URL 就能插圖。
- 圖片支持縮放、響應式。
- 如果要擴展成 上傳圖片到服務器再插入,只要改
addImage
方法,走接口拿到url
再setImage
即可。
👉 總結一句話:
- 想做 Notion/Google Docs 類現代應用 → 用 Tiptap。
- 想做 CMS/傳統后臺表單 → 用 TinyMCE。