不能用 contenteditable + 浏览器原生 undo/redo,因其撤销栈脆弱、三端行为不一致、execcommand 已废弃;应存轻量逻辑快照(如二维数组),配合显式 history/redo 栈与光标选区序列化还原。

为什么不能用 contenteditable + 浏览器原生 undo/redo
浏览器对 contenteditable 的隐式撤销栈极其脆弱:只要执行一次 td.innerHTML = newValue 或监听到中文输入法的 compositionend 之后才触发 input,就大概率丢掉中间状态;Safari 常跳步、Chrome 合并连续输入、Firefox 过于敏感——三者行为不一致,且无法序列化光标位置。调用 document.execCommand('undo') 在 Chrome 91+ 直接报错 TypeError: document.execCommand is not a function,已彻底废弃。
该存什么状态:DOM 结构还是逻辑数据?
别存整个 table.outerHTML,太重、含无意义属性、diff 困难、还原时丢失焦点。推荐存轻量级逻辑快照,比如:
如果你了解HTML,CSS和JavaScript,您已经拥有所需的工具开发Android应用程序。本动手本书展示了如何使用这些开源web标准设计和建造,可适应任何Android设备的应用程序 - 无需使用Java。您将学习如何创建一个在您选择的平台的Android友好的网络应用程序,然后转换与自由PhoneGap框架到一个原生的Android应用程序。了解为什么设备无关的移动应用是未来的潮流,并开始构建应用程序,提供更
- 二维数组
[[cell00, cell01], [cell10, cell11]],每个单元格只存文本值或结构化对象(如{ value: '123', formula: '=SUM(A1:B1)' }) - 若支持公式/格式,额外记录
formatMap映射:键为'R1C1',值为字体、背景色、对齐方式等 - 用
JSON.stringify(JSON.parse(JSON.stringify(data)))做浅层深拷贝够用;含Date或Map时改用structuredClone()(注意 Safari 16.4+ 才支持)
怎么设计 undo/redo 栈才不丢状态
核心是两个数组 + 显式状态快照 + 边界控制:
- 每次有效编辑(非空修改、非重复值)后,调用
saveState():把当前逻辑数据深拷贝推入historyStack,同时清空redoStack -
undo()弹出historyStack最后一项,压入redoStack,再用该快照重绘表格并恢复光标(需提前保存lastFocusedCell和selectionRange) - 设上限
MAX_HISTORY = 50,push 前检查长度,超限时historyStack.shift()踢掉最早一条 - 键盘快捷键(
Ctrl+Z/Cmd+Z)必须和按钮共用同一套状态更新逻辑,否则视觉与实际不一致
光标和选区还原为什么最难
用户双击进单元格编辑、按方向键跳转、Shift+方向键多选——这些操作产生的焦点和选区状态,浏览器不提供标准序列化接口。你得自己捕获:
- 在
focusin时记录document.activeElement和其rowIndex/cellIndex - 在
selectionchange时用window.getSelection()+getRangeAt(0)提取起止位置,转成相对坐标(如{ start: 'R2C3', end: 'R2C5' }) - 还原时不能只
cell.focus(),要先cell.contentEditable = 'true',再用range.selectNodeContents(cell)恢复选区 - 中文输入法下,
compositionstart到compositionend期间禁止快照,否则会存入未完成的输入内容
selection 或 focus 序列化,用户一撤销就“找不着北”,得手动点回去——体验断层就在这毫秒之间。前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!










