将 Tiptap 集成到 Svelte 中
完全指南
由于官方 Tiptap 文档缺乏相关信息,我在将这个流行的文本编辑器集成到 Svelte 项目时遇到了困难。本教程将补充缺失的内容,并提供一份全面的指南,帮助你在 Svelte 中成功运行 Tiptap。
发布于 Aug 6, 2025 • 4 分钟阅读

介绍
在构建我的 Svelte 项目时,我需要一个功能强大、可扩展的富文本编辑器,能够支持自定义以及现代化的用户体验特性。经过对现有编辑器的研究,我选择了 Tiptap,因为它具有灵活性、活跃的社区开发,以及基于 ProseMirror 的现代架构。
然而,将 Tiptap 集成到 Svelte 环境中并不像我预想的那样简单。虽然官方文档对 React 和 Vue 的支持非常全面,但对于 Svelte 用户几乎没有任何指导。
本教程旨在填补这一空白,帮助其他人顺利地在 Svelte 项目中运行 Tiptap。
为什么现有的 Tiptap 文档不够用
我遇到的几个痛点:
没有如何在编辑器内外传递数据的示例
官方指南没有涉及如何在 Tiptap 内部状态和 Svelte 的响应式系统之间进行绑定。添加控制按钮时出现错误
当我尝试添加自定义按钮来控制格式时,会遇到一些意外错误,这与 Tiptap 处理命令和编辑器状态的方式有关。缺少控制面板样式的指导
几乎没有关于如何美化控制按钮和面板,或者如何在 UI 中反映当前格式状态的帮助。
本教程将解决以上所有问题。
安装与初始化
按照官方的 Tiptap 指南进行安装:
让数据流动起来
下面介绍如何以响应式的方式在 Tiptap 中传入数据并获取更新。
示例:双向绑定
以下是 Tiptap.svelte
的示例代码,用于绑定编辑器的数据:
<script lang="ts">
import "./Tiptap-styles.scss";
import StarterKit from "@tiptap/starter-kit";
import { Editor } from "@tiptap/core";
import { onMount, onDestroy } from "svelte";
import { TableKit } from '@tiptap/extension-table'
/** 输入/输出 */
export let html: string;
/** 仅输出 */
export let editor: Editor;
let rootEl: HTMLDivElement;
let lastHTML: string | null = null;
onMount(onLoad);
function onLoad() {
createEditor();
}
function createEditor() {
editor = new Editor({
element: rootEl,
extensions: [
StarterKit, TableKit
],
content: html,
onTransaction: () => {
// 强制重新渲染,以确保 `editor.isActive` 正常工作
editor = editor;
},
onUpdate: ({ editor }) => {
html = lastHTML = editor.getHTML();
},
editable: true
});
lastHTML = html;
}
export function forceReload() {
if (editor) {
editor.destroy();
}
createEditor();
}
// 当 html 是由外部修改而不是编辑器本身修改时
$: html != lastHTML && forceReload();
onDestroy(() => {
if (editor) {
editor.destroy();
}
});
</script>
<div bind:this={rootEl} class="html-editor"></div>
添加控制面板
问题:添加按钮时报错
我第一次添加控制按钮(例如加粗、斜体)时遇到了如下错误:
The editor view is not available. Cannot access view['hasFocus']. The editor may not be mounted yet.
in <unknown>
in Tiptap.svelte
in +page.svelte
in +layout.svelte
in root.svelte
后来我发现问题出在这行代码:
disabled={!editor.can().chain().focus().toggleBold().run()}
这是因为我们在编辑器尚未初始化完成之前调用了 editor.can()
方法。
解决方法是:
要么删除
disabled
属性要么在调用之前增加编辑器就绪的判断。
示例:自定义控制面板
在 .html-editor
之前添加以下代码:
{#if editor}
<div class="control-group">
<!-- 文本格式控制 -->
<div class="button-group">
<button
type="button"
on:click={() => editor.chain().focus().toggleBold().run()}
class={editor.isActive("bold") ? "is-active" : ""}
>
Bold
</button>
...
<!-- 其余按钮代码保持不变 -->
</div>
<div class="separator"></div>
<!-- 表格控制 -->
<div class="button-group">
<button type="button" on:click={() => editor.chain().focus().insertTable({ rows: 3, cols: 3, withHeaderRow: true }).run()}>
Insert table
</button>
...
</div>
</div>
{/if}
自定义编辑器内容样式
Tiptap 不会提供任何内置样式,你需要自己定义。
可以使用 class 或 CSS 选择器来自定义编辑器内容。
示例 CSS
.html-editor {
height: 100%;
display: flex;
flex-direction: column;
padding: 0.5rem;
}
.html-editor :global(.ProseMirror) {
height: 100%;
flex: 1;
}
.html-editor :global(.ProseMirror:focus-visible) {
outline: none;
}
.control-group {
position: sticky;
top: 0;
z-index: 10;
background: #f5f5f5;
border-bottom: 1px solid #ddd;
padding: 0.5rem;
margin-bottom: 0;
flex-shrink: 0;
}
.button-group {
display: flex;
flex-wrap: wrap;
gap: 0.25rem;
margin-bottom: 0.25rem;
}
.button-group button {
padding: 0.25rem 0.5rem;
font-size: 0.75rem;
font-weight: 500;
line-height: 1.2;
color: #333;
background-color: #fff;
border: 1px solid #ccc;
border-radius: 0.25rem;
cursor: pointer;
transition: background-color 0.15s ease;
min-width: auto;
white-space: nowrap;
}
.button-group button:hover {
background-color: #f0f0f0;
}
.button-group button:focus {
outline: 2px solid #007acc;
outline-offset: 1px;
}
.button-group button.is-active {
background-color: #333;
color: white;
border-color: #333;
}
.button-group button.is-active:hover {
background-color: #555;
border-color: #555;
}
.separator {
width: 100%;
height: 0.25rem;
}
添加额外扩展
默认情况下,Tiptap 文本编辑器不包含 链接、表格、图片 等功能。
要添加这些功能,你需要安装相应的扩展。
结论
Tiptap 一旦运行起来,就是一个非常优秀的编辑器,但在与 Svelte 集成时,需要一些额外的处理,以及对 Tiptap 状态管理和命令机制的理解。
本教程演示了:
如何在 Svelte 中初始化 Tiptap
如何在编辑器与外部传递数据
如何构建功能性控制面板
如何自定义编辑器和控制按钮样式
希望这些内容能弥补官方文档的不足,节省你自己摸索的时间。