当前位置: 首页 > news >正文

TipTap 富文本编辑器在小说写作中的应用实践

🚀 TipTap 富文本编辑器在小说写作中的应用实践

💡 本文详细介绍如何在 Electron + Vue 3 项目中集成 TipTap 富文本编辑器,实现专业的写作体验,包括自定义扩展、实时统计、自动保存等核心功能。

📋 目录

  • 项目背景
  • TipTap 技术架构
  • 核心功能实现
  • 自定义扩展开发
  • 性能优化策略
  • 用户体验优化
  • 总结与展望

🎯 项目背景

在开发 51mazi 小说写作软件时,我们需要一个功能强大、可扩展性强的富文本编辑器。经过技术选型对比,最终选择了基于 ProseMirror 的 TipTap 编辑器,它提供了:

  • 🎨 丰富的编辑功能: 支持粗体、斜体、对齐等基础格式
  • 🔧 高度可扩展: 支持自定义扩展和插件
  • 优秀的性能: 基于 ProseMirror 的高性能架构
  • 🎯 Vue 3 集成: 原生支持 Vue 3 Composition API

编辑器界面

专业的写作编辑器界面 - 基于 TipTap 的富文本编辑器

🏗️ TipTap 技术架构

技术栈选择

// package.json 依赖
{"@tiptap/vue-3": "^2.12.0","@tiptap/starter-kit": "^2.12.0","@tiptap/extension-bold": "^2.12.0","@tiptap/extension-italic": "^2.12.0","@tiptap/extension-text-align": "^2.12.0"
}

核心架构设计

// src/renderer/src/components/EditorPanel.vue
import { Editor, EditorContent } from '@tiptap/vue-3'
import StarterKit from '@tiptap/starter-kit'
import Bold from '@tiptap/extension-bold'
import Italic from '@tiptap/extension-italic'
import TextAlign from '@tiptap/extension-text-align'const editor = new Editor({extensions: [StarterKit,Bold,Italic,TextAlign.configure({ types: ['heading', 'paragraph'] }),TabInsert, // 自定义 Tab 键扩展Collapsible // 自定义折叠扩展],content: editorStore.content,editorProps: {attributes: {style: () =>`font-family: ${fontFamily.value}; font-size: ${fontSize.value}; line-height: ${lineHeight.value}; text-align: ${align.value}; white-space: pre-wrap;`}},onUpdate: ({ editor }) => {const content = editor.getText()editorStore.setContent(content)// 防抖自动保存if (saveTimer) clearTimeout(saveTimer)saveTimer = setTimeout(() => {autoSaveContent()}, 1000)}
})

🔧 核心功能实现

1. 编辑器初始化与配置

// 编辑器配置
const editorConfig = {extensions: [StarterKit,Bold,Italic,TextAlign.configure({ types: ['heading', 'paragraph'] }),TabInsert,Collapsible],content: editorStore.content,editorProps: {attributes: {style: () => getEditorStyle()}},onUpdate: handleContentUpdate,onFocus: handleEditorFocus,onBlur: handleEditorBlur
}// 获取编辑器样式
function getEditorStyle() {return `font-family: ${fontFamily.value}; font-size: ${fontSize.value}; line-height: ${lineHeight.value}; text-align: ${align.value}; white-space: pre-wrap;padding: 20px;min-height: 500px;outline: none;`
}

2. 工具栏功能实现

<template><div class="editor-toolbar"><!-- 字体选择 --><el-select v-model="fontFamily" class="toolbar-item" size="small"><el-option label="默认" value="inherit" /><el-option label="宋体" value="SimSun" /><el-option label="微软雅黑" value="Microsoft YaHei" /><el-option label="楷体" value="KaiTi" /><el-option label="黑体" value="SimHei" /></el-select><!-- 字号选择 --><el-select v-model="fontSize" class="toolbar-item" size="small"><el-option label="12px" value="12px" /><el-option label="14px" value="14px" /><el-option label="16px" value="16px" /><el-option label="18px" value="18px" /><el-option label="20px" value="20px" /></el-select><!-- 行高选择 --><el-select v-model="lineHeight" class="toolbar-item" size="small"><el-option label="1.2" value="1.2" /><el-option label="1.4" value="1.4" /><el-option label="1.6" value="1.6" /><el-option label="1.8" value="1.8" /><el-option label="2.0" value="2" /></el-select><!-- 格式按钮 --><el-buttonclass="toolbar-item"size="small":type="isBold ? 'primary' : 'default'"@click="toggleBold"><b>B</b></el-button><el-buttonclass="toolbar-item"size="small":type="isItalic ? 'primary' : 'default'"@click="toggleItalic"><i>I</i></el-button><!-- 操作按钮 --><el-button size="small" class="toolbar-item" @click="copyContent"><el-icon><DocumentCopy /></el-icon></el-button><el-button size="small" class="toolbar-item" type="primary" @click="saveContent">保存</el-button></div>
</template><script setup>
import { ref, computed } from 'vue'
import { DocumentCopy } from '@element-plus/icons-vue'const fontFamily = ref('inherit')
const fontSize = ref('16px')
const lineHeight = ref('1.6')
const align = ref('left')// 格式状态
const isBold = computed(() => editor.value?.isActive('bold'))
const isItalic = computed(() => editor.value?.isActive('italic'))// 切换格式
function toggleBold() {editor.value?.chain().focus().toggleBold().run()
}function toggleItalic() {editor.value?.chain().focus().toggleItalic().run()
}// 复制内容
function copyContent() {const content = editor.value?.getText()if (content) {navigator.clipboard.writeText(content)ElMessage.success('内容已复制到剪贴板')}
}
</script>

3. 实时统计功能

// src/renderer/src/stores/editor.js
export const useEditorStore = defineStore('editor', () => {const content = ref('')const typingStartTime = ref(null)const initialWordCount = ref(0)const typingSpeed = ref({perMinute: 0,perHour: 0})// 计算当前字数const chapterWords = computed(() => {return content.value.length})// 开始计时function startTypingTimer() {if (!typingStartTime.value) {typingStartTime.value = Date.now()initialWordCount.value = chapterWords.value}}// 更新码字速度function updateTypingSpeed() {if (!typingStartTime.value) returnconst now = Date.now()const timeElapsed = (now - typingStartTime.value) / 1000const wordsTyped = chapterWords.value - initialWordCount.valueif (timeElapsed > 0) {typingSpeed.value = {perMinute: Math.round((wordsTyped / timeElapsed) * 60),perHour: Math.round((wordsTyped / timeElapsed) * 3600)}}}// 设置内容时触发统计function setContent(newContent) {content.value = newContentstartTypingTimer()updateTypingSpeed()}return {content,chapterWords,typingSpeed,setContent,resetTypingTimer}
})

🔧 自定义扩展开发

1. Tab 键插入扩展

// src/renderer/src/extensions/TabInsert.js
import { Extension } from '@tiptap/core'export const TabInsert = Extension.create({name: 'tabInsert',addKeyboardShortcuts() {return {Tab: () => {this.editor.commands.insertContent('\t')return true}}}
})

2. 折叠功能扩展

// src/renderer/src/extensions/Collapsible.js
import { Extension } from '@tiptap/core'
import { Plugin, PluginKey } from 'prosemirror-state'export const Collapsible = Extension.create({name: 'collapsible',addProseMirrorPlugins() {return [new Plugin({key: new PluginKey('collapsible'),props: {handleDOMEvents: {keydown: (view, event) => {// 处理折叠逻辑if (event.key === 'Tab' && event.shiftKey) {// 处理 Shift+Tab 折叠return true}return false}}}})]}
})

3. 自定义样式扩展

// src/renderer/src/extensions/CustomStyles.js
import { Extension } from '@tiptap/core'export const CustomStyles = Extension.create({name: 'customStyles',addGlobalAttributes() {return [{types: ['paragraph'],attributes: {customStyle: {default: null,parseHTML: element => element.getAttribute('data-custom-style'),renderHTML: attributes => {if (!attributes.customStyle) return {}return {'data-custom-style': attributes.customStyle,style: attributes.customStyle}}}}}]}
})

⚡ 性能优化策略

1. 防抖自动保存

// 防抖自动保存实现
let saveTimer = nullfunction handleContentUpdate({ editor }) {const content = editor.getText()editorStore.setContent(content)// 防抖保存if (saveTimer) clearTimeout(saveTimer)saveTimer = setTimeout(() => {autoSaveContent()}, 1000)
}async function autoSaveContent() {try {const file = editorStore.fileif (!file) returnconst content = editorStore.contentawait window.electron.writeFile(file.path, content)console.log('自动保存成功')} catch (error) {console.error('自动保存失败:', error)}
}

2. 内容渲染优化

// 纯文本转 HTML 优化
function plainTextToHtml(text) {if (!text) return ''const lines = text.split('\n')const htmlLines = lines.map((line) => {// 替换 Tab 为空格let html = line.replace(/\t/g, '&nbsp;&nbsp;&nbsp;&nbsp;')// 替换连续空格html = html.replace(/ {2,}/g, (match) => '&nbsp;'.repeat(match.length))return html ? `<p>${html}</p>` : ''})return htmlLines.join('')
}

3. 内存管理

// 组件销毁时清理资源
onBeforeUnmount(async () => {if (saveTimer) clearTimeout(saveTimer)// 保存最后的内容await autoSaveContent()// 重置码字统计editorStore.resetTypingTimer()// 销毁编辑器editor.value && editor.value.destroy()
})

🎨 用户体验优化

1. 响应式工具栏

<template><div class="editor-stats"><span class="word-count">章节字数:{{ chapterWords }}</span><span v-if="typingSpeed.perMinute > 0" class="typing-speed">码字速度:{{ typingSpeed.perMinute }}字/分钟 ({{ typingSpeed.perHour }}字/小时)</span></div>
</template><style lang="scss" scoped>
.editor-stats {display: flex;justify-content: space-between;align-items: center;padding: 10px 20px;background-color: var(--bg-soft);border-top: 1px solid var(--border-color);font-size: 12px;color: var(--text-secondary);
}.word-count {font-weight: 500;
}.typing-speed {color: var(--text-muted);
}
</style>

2. 快捷键支持

// 快捷键配置
const keyboardShortcuts = {'Ctrl+S': () => saveContent(),'Ctrl+B': () => toggleBold(),'Ctrl+I': () => toggleItalic(),'Ctrl+Z': () => editor.value?.chain().focus().undo().run(),'Ctrl+Y': () => editor.value?.chain().focus().redo().run()
}// 注册快捷键
onMounted(() => {document.addEventListener('keydown', (event) => {const key = getKeyCombination(event)const handler = keyboardShortcuts[key]if (handler) {event.preventDefault()handler()}})
})function getKeyCombination(event) {const keys = []if (event.ctrlKey) keys.push('Ctrl')if (event.shiftKey) keys.push('Shift')if (event.altKey) keys.push('Alt')keys.push(event.key.toUpperCase())return keys.join('+')
}

3. 主题适配

// 编辑器主题样式
.editor-content {.ProseMirror {background-color: var(--bg-primary);color: var(--text-primary);border: 1px solid var(--border-color);border-radius: 8px;min-height: 500px;padding: 20px;&:focus {outline: none;border-color: var(--primary-color);box-shadow: 0 0 0 2px var(--primary-color-alpha);}p {margin: 0 0 1em 0;line-height: 1.6;}p:last-child {margin-bottom: 0;}}
}

📊 功能特性总结

✅ 已实现功能

  • 基础编辑: 文本输入、删除、选择
  • 格式控制: 粗体、斜体、对齐方式
  • 字体设置: 字体族、字号、行高
  • 实时统计: 字数统计、码字速度
  • 自动保存: 防抖机制、本地存储
  • 快捷键: 常用操作快捷键支持
  • 主题适配: 多主题模式支持

🚀 技术亮点

  1. 高性能: 基于 ProseMirror 的高性能架构
  2. 可扩展: 支持自定义扩展和插件
  3. 用户友好: 直观的工具栏和统计信息
  4. 数据安全: 自动保存和错误处理机制

📝 总结与展望

TipTap 富文本编辑器在 51mazi 项目中的成功应用,展示了如何利用现代化的前端技术构建专业的写作工具。通过合理的架构设计、性能优化和用户体验优化,我们实现了一个功能完整、性能优秀的编辑器。

🎯 技术价值

  • 架构设计: 模块化的扩展系统
  • 性能优化: 防抖、懒加载等优化策略
  • 用户体验: 实时统计、快捷键等交互优化
  • 可维护性: 清晰的代码结构和状态管理

🔮 未来规划

  • 更多格式: 支持更多文本格式和样式
  • 协作功能: 多人协作编辑支持
  • 版本控制: 更完善的版本管理功能
  • 插件生态: 支持第三方插件扩展

📚 相关链接

  • 项目地址: GitHub - 51mazi,给个 Star 哦~
  • TipTap 官网: https://tiptap.dev
  • 技术栈: TipTap + Vue 3 + Electron + Element Plus

🏷️ 标签

#TipTap #富文本编辑 #Vue3 #Electron #小说写作 #前端开发 #性能优化


💡 如果这篇文章对你有帮助,请给个 ⭐️ 支持一下!

http://www.dtcms.com/a/291427.html

相关文章:

  • PyCharm 未正确关联 .jpg 为图片格式
  • 重学前端008 --- 响应式网页设计 CSS 无障碍 Quiz
  • React探索高性能Tree树组件实现——react-window、react-vtree
  • 安装cobalt_Strike_4.7
  • B树、B+树的区别及MySQL为何选择B+树
  • Python 使用期物处理并发(使用concurrent.futures模块启动 进程)
  • 【Elasticsearch】BM25的discount_overlaps参数
  • 卷积神经网络(CNN)原理
  • 零拷贝技术(Zero-Copy)
  • OneCode 3.0 @APIEventAnnotation 注解速查手册
  • 从 Hi3861 平台到 WS63 星闪平台的程序移植全解析
  • 网络编程之 UDP:用户数据报协议详解与实战
  • 二分查找:区间内查询数字的频率
  • 网络协议(三)网络层 IPv4、CIDR(使用子网掩码进行网络划分)、NAT在私网划分中的应用
  • 大模型——上下文工程 (Context Engineering) – 现代 AI 系统的架构基础
  • c语言进阶 自定义类型 枚举,联合
  • 【LeetCode 热题 100】208. 实现 Trie (前缀树)
  • Linux下SPI设备驱动开发
  • 1.Java中的异常有哪些?异常处理机制呢?
  • C# 异常处理
  • 统计与大数据分析专业转型金融行业指南
  • makefile-- 其他函数
  • Linux PCI总线子系统
  • 网络基础DAY15-RSTP
  • OpenGL鼠标控制沿着指定轴旋转
  • linux --frp内网穿透
  • 低速信号设计之 RMII
  • 服务器系统时间不准确怎么办?
  • C++ 中的默认构造函数:非必要,不提供
  • 缓存数组,并遍历循环读取数组