tiptap介绍
tiptap是一个"无头"的富文本编辑器。"无头",即,非开箱即用,不提供功能面板,需要用户自己开发功能面板的UI。
官方文档地址:Introduction -- Tiptap
tiptap安装(以vue为例)
新建一个vue工程
shell
pnpm create vue@latest
# 输入项目名称,配置工程内容等
pnpm i
安装tiptap依赖
shell
pnpm install @tiptap/vue-3 @tiptap/pm @tiptap/starter-kit
初始化tiptap
html
<template>
<EditorContent :editor="editor"></EditorContent>
</template>
<script setup>
import { useEditor, EditorContent } from '@tiptap/vue-3'
import { StarterKit } from '@tiptap/starter-kit'
const editor = useEditor({
content: 'hello tiptap',
extensions: [StarterKit]
})
</script>
预览效果如下

好的,相信你已经学会了tiptap编辑器的基本内容,接下来我们就去开发一个飞书文档吧(大雾)。
首先对这3个依赖进行作用分析:
@tiptap/vue3
提供vue3里的组件引用与editor
暴露,同时,所有在@tiptap/core
中导出的api,也会在该库中导出

@tiptap/pm
提供tiptapProseMirror
的api,tiptap是基于ProseMirror
开发,pm库属于必要库。 @tiptap/start-kit
为插件合集库,包含了如下内容:

需要配置上述插件能力的地方,可以直接从start-kit
中进行处理,也可以不使用start-kit
包,自身配置一个最简易的文本输入可通过配置Document
、Text
、Paragraph
3个插件实现。
tiptap功能面板实现
首先准备一份富文本图标(本文从iconfont上白嫖一份公开库:富文本编辑器图标),并实现一下基本UI和交互逻辑

tiptap的outline可以通过注入属性的方式去除,根据当前的UI,需要实现如下功能:
- 加粗 Bold -- Tiptap
- 斜体 Italic -- Tiptap
- 有序列表 OrderedList -- Tiptap
- 无序列表 BulletList -- Tiptap
- 分隔线 HorizontalRule -- Tiptap
- 代码块 CodeBlock -- Tiptap
根据API实现代码:
html
<template>
<div style="border: 2px solid #dcdfe6; width: 600px; height: 300px">
<div class="tool-panel">
<div
:class="['btn', editor.isActive('bold') && 'active']"
@click="editor.chain().focus().toggleBold().run()"
>
<i class="icon iconfont icon-bold"></i>
</div>
<div
:class="['btn', editor.isActive('italic') && 'active']"
@click="editor.chain().focus().toggleItalic().run()"
>
<i class="icon iconfont icon-italic"></i>
</div>
<div
:class="['btn', editor.isActive('orderedList') && 'active']"
@click="editor.chain().focus().toggleOrderedList().run()"
>
<i class="icon iconfont icon-list-order"></i>
</div>
<div
:class="['btn', editor.isActive('bulletList') && 'active']"
@click="editor.chain().focus().toggleBulletList().run()"
>
<i class="icon iconfont icon-list-disorder"></i>
</div>
<div
:class="['btn', editor.isActive('blockquote') && 'active']"
@click="editor.chain().focus().toggleBlockquote().run()"
>
<i class="icon iconfont icon-double-quotes-left"></i>
</div>
<div class="btn" @click="editor.chain().focus().setHorizontalRule().run()">
<i class="icon iconfont icon-move-horizontal"></i>
</div>
<div class="btn" @click="editor.chain().focus().toggleCodeBlock().run()">
<i class="icon iconfont icon-code-block"></i>
</div>
</div>
<div style="height: calc(100% - 30px)">
<EditorContent :editor="editor" class="tiptap-container"></EditorContent>
</div>
</div>
</template>
<script setup>
import { useEditor, EditorContent } from '@tiptap/vue-3'
import { StarterKit } from '@tiptap/starter-kit'
const editor = useEditor({
content: 'hello tiptap',
extensions: [StarterKit],
editorProps: {
attributes: {
class: 'tiptap-editor'
}
}
})
</script>
<style>
.tiptap-editor {
width: 100%;
height: 100%;
outline: none;
}
.tiptap-container {
width: 100%;
height: 100%;
}
.tool-panel {
border-bottom: 2px solid #dcdfe6;
height: 30px;
display: flex;
align-items: center;
}
.icon {
font-size: 20px;
}
.btn {
width: 30px;
height: 30px;
line-height: 30px;
text-align: center;
cursor: pointer;
}
.active {
background-color: #909399;
}
blockquote {
padding-left: 1rem;
border-left: 3px solid rgb(144, 143, 153);
}
pre {
background: #0d0d0d;
color: #fff;
font-family: 'JetBrainsMono', monospace;
padding: 0.75rem 1rem;
border-radius: 0.5rem;
}
pre code {
color: inherit;
padding: 0;
background: none;
font-size: 0.8rem;
}
</style>
效果图如下:

editor.isActive
可以检测当前正在激活的node
和mark
,可用于确认当前文本为哪种格式(示例中已激活的粗体)

各图标绑定了点击事件,使用editor.chain().focus().xxx().run()
。这事使用tiptap中的command
能力来使富文本进行指定交互,除了上述方法外,也可以通过editor.commands.xxx()
,但是还是推荐使用chain
来实现。chain
的使用实际上可以进行复合命令输入,即可以实现一次注入多个命令,例如editor.chain().focus().toggleBold().toggleItalic().run()
可以同时实现文本的粗体和斜体切换。
重要 :如果未使用run()
,命令不会生效,开发时注意不要丢了run的调用。
简易的起始篇到此结束