2026-03-15 11:16:46
实现一个支持撤销重做功能的富文本编辑器,需结合contenteditable、事件监听、状态管理和DOM操作,核心步骤如下:
1. 基础结构与事件监听input:捕获内容变更(但需过滤无效事件)。
keydown:监听快捷键(如Ctrl+Z/Ctrl+Y)。
beforeinput/compositionstart/end:处理中文输入法等复合输入,避免中间状态误记录。
MutationObserver:监听DOM结构变化,辅助判断真实内容变更。
内容:innerHTML或序列化后的DOM结构。
选区信息:光标位置(start/end)和节点路径(如[0, 0]表示第1个子节点的第1个子节点)。
const stateSnapshot = { content: "<p>Hello</p>", selection: { start: 5, end: 5, nodePath: [0, 0] }};每次用户操作触发可感知变更时,将当前状态压入undoStack,并清空redoStack。
撤销时从undoStack弹出状态,恢复内容与光标,并压入redoStack;重做逻辑相反。
连续输入:首次输入和停止1-2秒后记录一次,避免频繁快照。
格式化操作(如加粗、对齐):立即生成新快照。
MutationObserver:监听DOM变化,过滤无实际内容的变更(如属性修改)。
还原内容:直接设置editor.innerHTML为快照内容。
还原光标:通过nodePath定位节点,使用Range重建选区。
function undo() { if (undoStack.length === 0) return; const state = undoStack.pop(); redoStack.push(getCurrentState()); editor.innerHTML = state.content; restoreSelection(state.selection);}function restoreSelection({ start, end, nodePath }) { const range = document.createRange(); let node = editor; nodePath.forEach(index => node = node.childNodes[index]); range.setStart(node, start); range.setEnd(node, end); const selection = window.getSelection(); selection.removeAllRanges(); selection.addRange(range);}若不想从零实现,可选用内置撤销重做的富文本框架:
实现撤销重做的关键在于:
理解底层原理后,可根据需求选择自行开发或基于框架扩展。