Vue富文本编辑器撤销功能导致光标位置错乱怎么办?

___子怡 阅读 15

我在用contenteditable做富文本编辑器时,想通过保存历史快照实现撤销功能。但每次undo后光标会跳到开头,而且频繁操作会内存溢出。

现在用Vue维护一个history数组,在input事件里push当前innerHTML。undo方法直接取前一个快照赋值给content,但发现:


<template>
  <div 
    contenteditable 
    @input="saveState" 
    v-html="content"
  ></div>
</template>

<script>
export default {
  data() {
    return {
      content: '<p>初始内容</p>',
      history: []
    }
  },
  methods: {
    saveState() {
      this.history.push(this.content)
      this.history = this.history.slice(-20) // 限制长度
    },
    undo() {
      this.history.pop()
      this.content = this.history[this.history.length-1]
    }
  }
}
</script>

这样实现后,撤销确实生效了,但光标总跳到开头,而且快速输入几十次后页面明显卡顿。有没有更好的历史记录管理方案?

我来解答 赞 2 收藏
二维码
手机扫码查看
1 条解答
萌新.维通
你这个方案有两个核心问题,一个是光标位置丢失,另一个是性能问题。我们来逐一解决。

光标跳到开头是因为直接修改了content的值,导致浏览器重新渲染整个DOM结构,自然会重置光标。要解决这个问题,需要在undo的时候手动保存和恢复光标位置。可以借助Selection和Range API来处理:


getCursorPosition() {
const selection = window.getSelection()
if (selection.rangeCount === 0) return
return selection.getRangeAt(0)
},
restoreCursorPosition(range) {
const selection = window.getSelection()
selection.removeAllRanges()
selection.addRange(range)
}


在undo方法里调用这两个辅助方法:


undo() {
if (this.history.length < 2) return // 至少保留一个状态

const currentRange = this.getCursorPosition()
this.history.pop()
this.content = this.history[this.history.length-1]

this.$nextTick(() => {
if (currentRange) {
this.restoreCursorPosition(currentRange)
}
})
}


至于性能问题,频繁push完整HTML确实吃内存,建议改成存增量更新。比如只记录每次变化的差异,或者限制history长度在10以内就够了。另外,不要在每个input事件都存快照,可以加个防抖:


data() {
return {
content: '<p>初始内容</p>',
history: [],
saveTimer: null
}
},
methods: {
saveState: function() {
clearTimeout(this.saveTimer)
this.saveTimer = setTimeout(() => {
this.history.push(this.content)
this.history = this.history.slice(-10)
}, 300)
}
}


这样既解决了光标问题,又优化了性能。记得测试下主流浏览器兼容性,尤其是IE这种老古董可能需要额外打补丁。做富文本编辑器确实挺烦人的,慢慢调吧。
点赞 1
2026-02-15 23:08