如何撤销在contenteditable div中通过脚本执行的内容修改操作
contenteditable脚本修改后实现原子撤销的可行方案
完全可行,目前有两种成熟的实现方式,都可以复用浏览器原生撤销栈,不需要自己实现整套撤销重做逻辑。
方案1:使用document.execCommand(兼容全浏览器)
浏览器原生撤销栈只会追踪两类内容修改:用户的交互输入,以及通过document.execCommand触发的内容修改。直接操作DOM节点修改内容的方式,不会被浏览器纳入撤销追踪,才会出现撤销不全的问题。
你给出的示例可以改成如下实现,就能实现一次撤销删除两个a的效果:
const editor = document.getElementById("editor") editor.addEventListener("keydown", ev => { if (ev.key === 'a') { ev.preventDefault() // insertText命令的修改会被当成单个原子撤销单元 document.execCommand('insertText', false, 'aa') } })
这个方案的优势是兼容性极强,所有支持contenteditable的浏览器都可以正常运行;缺点是execCommand已被W3C标记为废弃,部分复杂场景下的行为是黑盒不好调试。
方案2:使用UndoManager API(现代浏览器标准方案)
Chrome 108+、Edge 108+、Safari 16.4+已经支持了标准的UndoManager API,这个API允许你手动将任意DOM修改、选区调整打包成单个原子撤销单元,直接接入浏览器原生撤销栈。
使用方式如下:
const editor = document.getElementById("editor") editor.addEventListener("keydown", ev => { if (ev.key === 'a') { ev.preventDefault() const undoManager = document.undoManager // 开启事务,之后所有DOM和选区修改都会被归入同一个撤销条目 undoManager.beginTransaction() const sel = window.getSelection() const range = sel.getRangeAt(0) const node = range.startContainer; const value = node.nodeValue node.nodeValue = value + 'aa' const newPos = value.length + 2 range.setStart(node, newPos) range.setEnd(node, newPos) sel.removeAllRanges() sel.addRange(range) // 提交事务,完成原子操作打包 undoManager.commitTransaction() } })
这个方案是未来的标准实现,没有execCommand的黑盒问题,支持高度自定义操作内容,适合不需要兼容旧版本浏览器的业务场景。
内容的提问来源于stack exchange,提问作者farincz
相关产品推荐
相关产品推荐

