Vue.js - 富文本编辑器Tiptap使用详解4(自定义扩展1:Highlight)
作者:hangge | 2026-02-14 12:09
我们可以使用 Tiptap 的扩展机制来自定义或扩展编辑器功能(基于现有扩展定制,或者从头创建 Mark / Node / NodeView)。前文我介绍了如何使用 tiptap 官方提供的@tiptap/extension-highlight 扩展实现高亮功能(点击查看),本文还是以该功能为例,演示如何手动实现一个自定义高亮(Highlight)的扩展。
四、自定义实现高亮(Highlight)扩展
1,基本介绍
(1)首先我们需要熟悉如下一些概念:
- Extension:是 Tiptap 的扩展单元,底层基于 ProseMirror。Extension 可是 Node(块或内联内容)、Mark(文本内的格式/注释)或通用扩展(插件/命令/键盘等)。创建扩展时会实现 parseHTML、renderHTML、addCommands()、addAttributes()、addNodeView() 等钩子。
- Mark:用于内联文本样式(加粗、斜体、链接、下划线、highlight 等)。适合“标记”或注释文本。
- Node:文档树的节点(段落、图片、附件、表格、引用、嵌入组件等)。当需要可交互或包含子内容(content)时用 Node。
(2)开发扩展惯用步骤如下:
- 决定类型:Mark(inline)或 Node(block/inline、可含子内容)。
- 设计 schema(属性/attrs、content、group、inline/atom/defining 等)。
- 实现 parseHTML()(如何从 HTML 恢复)与 renderHTML()(如何输出 HTML)。可用 mergeAttributes() 帮助合并 attrs。
- 提供 addCommands() 将常用操作封装成命令,便于在 UI 中调用(editor.chain().focus().yourCommand().run())。
- 如需交互/复杂 UI,使用 NodeView,并在 Vue 中通过 VueNodeViewRenderer 渲染组件。
- 测试:editor.getJSON() / editor.getHTML() 验证文档模型及序列化结果。
2,实现扩展
我们创建一个名为 highlight.js 自定义扩展,代码如下:
import { Mark, mergeAttributes } from '@tiptap/core'
export default Mark.create({
name: 'highlight',
addOptions() {
return {
HTMLAttributes: {},
multicolor: false, // 默认不开启多色支持
}
},
// 根据 multicolor 决定要不要加入 color 属性
addAttributes() {
if (this.options.multicolor) {
return {
color: {
default: null,
parseHTML: element => {
// 尝试从 data-color 或 style 提取 color
return (
element.getAttribute('data-color') ||
(element.style && element.style.backgroundColor) ||
null
)
},
renderHTML: attrs => {
if (!attrs.color) return {}
return {
'data-color': attrs.color,
style: `background-color: ${attrs.color}`,
}
},
},
...this.options.HTMLAttributes,
}
}
// 不支持多色时,不暴露 color 属性
return {
...this.options.HTMLAttributes,
}
},
parseHTML() {
// 支持 <mark>,以及可能带 data-color 或 style 的 span/mark
const rules = [{ tag: 'mark' }]
if (this.options.multicolor) {
rules.push({ tag: 'span[data-color]' })
rules.push({ tag: 'span[style*="background-color"]' })
}
return rules
},
renderHTML({ HTMLAttributes }) {
// 如果没有 color 或 multicolor 被禁用,则渲染为 <mark>
if (!this.options.multicolor || !HTMLAttributes.color) {
return ['mark', mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), 0]
}
// multicolor 且有 color:渲染带 style 的 mark(或 span)
return [
'mark',
mergeAttributes(this.options.HTMLAttributes, HTMLAttributes),
0,
]
},
addCommands() {
return {
// setHighlight 可以接受 { color } 或 undefined
setHighlight:
attrs =>
({ commands }) => {
// 当 multicolor 关闭时忽略 color 字段
const cleanAttrs = this.options.multicolor && attrs
&& attrs.color ? { color: attrs.color } : {}
return commands.setMark(this.name, cleanAttrs)
},
unsetHighlight:
() =>
({ commands }) => {
return commands.unsetMark(this.name)
},
toggleHighlight:
attrs =>
({ commands, editor }) => {
// 如果 multicolor 开启并传入 color,则优先使用 setHighlight
if (this.options.multicolor && attrs && attrs.color) {
// 如果当前选区已高亮且颜色相同 -> 取消;否则设置为新颜色
if (editor.isActive(this.name, { color: attrs.color })) {
return commands.unsetHighlight()
} else {
return commands.setHighlight({ color: attrs.color })
}
}
// 无 color 情况下切换(simple toggle)
return commands.toggleMark(this.name)
},
}
},
addKeyboardShortcuts() {
// 示例:Ctrl/Cmd-Shift-H 作为高亮快捷键(不传颜色,效果为 toggle)
return {
'Mod-Shift-h': () => this.editor.commands.toggleHighlight(),
}
},
})
3,使用扩展
(1)自定义扩展创建后后我们就可以使用该扩展了,具体用法和官方提供的 Highlight 扩展一致。
(2)运行后,我们可以选中文字后应用高亮,并且可以选择不同颜色进行文字高亮,或者取消高亮。
<template>
<div class="editor-container">
<!-- 工具栏 -->
<div class="menu-bar" v-if="editor">
<!-- 高亮按钮(默认切换) -->
<button
:class="{ active: editor.isActive('highlight') }"
@click="toggleHighlight"
>
Highlight
</button>
<!-- 多颜色选择 -->
<span class="color-bar">
<button
v-for="c in colors"
:key="c"
class="color-btn"
:style="{ backgroundColor: c }"
@click="setHighlightColor(c)"
></button>
</span>
<!-- 取消高亮 -->
<button @click="clearHighlight">Clear</button>
</div>
<!-- 编辑器 -->
<EditorContent :editor="editor" class="editor-content"/>
</div>
</template>
<script>
import { Editor, EditorContent } from '@tiptap/vue-2'
import StarterKit from '@tiptap/starter-kit'
import Highlight from './highlight.js'
export default {
components: { EditorContent },
data() {
return {
editor: null,
// 可自定义更多颜色
colors: ['#ffeb3b', '#ff8a80', '#80d8ff', '#ccff90', '#ffd180'],
}
},
mounted() {
this.editor = new Editor({
extensions: [
StarterKit,
// multicolor: true 允许自定义颜色
Highlight.configure({
multicolor: true,
}),
],
content: `
<p>选中某些文字并使用上方颜色按钮,即可应用高亮颜色。</p>
`,
})
},
beforeDestroy() {
this.editor.destroy()
},
methods: {
toggleHighlight() {
this.editor.chain().focus().toggleHighlight().run()
},
setHighlightColor(color) {
this.editor.chain().focus().setHighlight({ color }).run()
},
clearHighlight() {
this.editor.chain().focus().unsetHighlight().run()
},
},
}
</script>
<style>
.editor-container {
border: 1px solid #ccc;
border-radius: 4px;
}
/* 工具栏 */
.menu-bar {
padding: 8px;
border-bottom: 1px solid #eee;
display: flex;
align-items: center;
gap: 10px;
}
/* 按钮样式 */
.menu-bar button {
padding: 6px 12px;
border: 1px solid #bbb;
background: #fafafa;
cursor: pointer;
border-radius: 4px;
}
.menu-bar button.active {
background: #ffe066;
}
/* 颜色按钮 */
.color-bar {
display: flex;
gap: 8px;
margin-left: 15px;
}
.color-btn {
width: 22px;
height: 22px;
cursor: pointer;
border-radius: 4px;
border: 1px solid #aaa;
padding: 0;
}
</style>
(2)运行后,我们可以选中文字后应用高亮,并且可以选择不同颜色进行文字高亮,或者取消高亮。
全部评论(0)