前端编辑器类项目实现用户操作撤销与反撤销
越来越多的前端用于编辑器类工具的开发,常见的如富文本编辑器、H5页面生成器、低代码平台etc... 对于这类编辑器的工具除去ctrl+c ctrl+v外 ,一般还需要有ctrl+z ctrl+y的功能。如何设计一个用户历史记录的队列才能更好的实现用户编辑的前进后退
一、历史记录是保存操作还是保存当前全部数据状态?
就是说假设用户有如下操作
我们是记录为
['a', 'ab' , 'abc']
还是
[ {type: 'add" value: 'a'}, {type: 'add" value: 'b'}, {type: 'add" value: 'c'}, ]
1、保存方式的选择
对于这个问题要看具体的项目类型,如单一类型的操作,数据量不大,可以直接保存每一步的操作时的全部数据状态,如简单的文本编辑器,而对于操作复杂,数据量大我们选用方式二。
2、方式二的实现
对于方式二,我们我看根据项目的操作自定义操作类型,如我们是一个H5编辑器,我们可以分类为:
- type: page -页面的操作, 记录当前页面的数据信息
- type: pageProps -记录单个页面的属性信息
- type: component -记录某个页面下的全部组件
- type: componentProps -记录某个组件的属性
在用户后退或前进时,我们可以根据上一步修改的数据,进行对应的恢复,或者逆向修改;
二、历史记录队列的创建与添加
确定了记录的内容,如何创建一个历史记录? 什么时间添加呢? 当前的状态又如何指向呢?
1、队列的创建
首先我们创建一个历史记录类,用一个数组保存数据,用一个变量为指针,指向用户当前的最新操作
export default class History {
constructor() {
this.historyStore = []
this.historyIndex = -1
this.max = 100;//最大记录数目
}
}
复制代码
2、指针的指向
假设用户有如下操作:
step | action |
---|---|
1 | a |
2 | b |
3 | c |
4 | d |
5 | back |
6 | back |
7 | e |
8 | f |
1)指针默认指向用户当前的最新操作
2)用户后退,指针后退
3)后退后再添加记录,删除当前指针后面的元素,再添加新的记录
代码实现为
// 新增记录
addItem(d) {
// 撤销后重新添加记录 删除撤销的记录
if(this.historyIndex != this.historyStore.length - 1){
let dif = this.historyStore.length - this.historyIndex - 1
this.historyStore.splice(this.historyIndex, dif)
}
// 新增记录
this.historyStore.push(d)
this.historyIndex ++ ;
// 超出
if(this.historyIndex.length > this.max){
this.historyStore.shift()
this.historyIndex --
}
}
// 后退
back() {
if(this.historyStore.length == 0 || this.historyIndex < 0) return;
this.historyIndex --
}
// 前进
go() {
if(this.historyStore.length == 0 || this.historyIndex >= this.historyStore.length) return;
this.historyIndex ++
}
复制代码
3、添加记录的时间节点
对于用户的操作,我们可以再数据更新前添加未更新前数据到记录,也可以在更新后记录后新后的数据
如用户有以下操作
step | action |
---|---|
1 | a |
2 | b |
3 | c |
4 | back |
(1)如果我们记录更新前的数据,到step 4时我们有如下数据记录
第一次后退时,需记录当前最后一步更新后的数据,以保证能前进到最新数据
(2)如果我们更新后得数据则需要在第一步时,则需有
- 在第一次添加记录前,先记录一次未修改前的全部数据
以确保后退到开始时候,有最初数据
这样每次我们后退时还须判断更新的记录,确定恢复的数据,
三、快捷键的实现
//按键摁下记录各个特殊键
let ctrlDown = false;
window.addEventListener('keydown', function (e) {
if (['Control', 'Meta'].includes(e.key)) {
ctrlDown = true;
}
if(ctrlDown && e.key == 'Z') //后退。。。
if(ctrlDown && e.key == 'Y') //前进。。。
})
// 松开按键
window.addEventListener('keyup', function (e) {
if (['Control', 'Meta'].includes(e.key)) {
ctrlDown = false;
}
})
// 浏览器脱离焦点,释放
window.onblur = function() {
ctrlDown = false;
};