編輯器等工具類如何設計用戶操作歷史記錄隊列實現前進後退
越來越多的前端用於編輯器類工具的開發,常見的如富文本編輯器、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;
};