本文已参与掘金创作者训练营第三期,详情查看:掘力计划|创作者训练营第三期正在进行,「写」出个人影响力。
前言
上篇文章我们实现了图片编辑器的辅助线,大多数的工具类型软件都支持撤销和重做,趁热打铁,我们图片编辑器的撤销重做的功能给实现一下,丰富我们的图片编辑器的功能。
演示
演示地址
实现流程
原理讲解
通过上面的演示我们分析有这三种类型的操作,分别是push
,undo
,redo
1. 在操作的过程中记录状态push
通过流程图我们可以看到,正常的操作直接记录状态就可以。
2.在操作过程中撤销操作undo
通过流程图我们可以看到在撤销1
操作后,当前的状态会到操作2
的状态。在进一步点击撤销2
会回到操作1
的状态
3.在操作过程中执行重做操作rudo
通过流程图我们可以看到在操作重做1
后,图片会回到操作2
的状态。
4.继续在操作的过程中记录状态again push
注意
:此时我们可以有两种操作
,一是继续点击重做
,图片会回到操作3
的状态。这个我们就不画流程图了。另一种可以直接在操作图片内的元素,此时要把操作3
的状态记录给移除掉
.流程图如下:
代码实现
大致思路是,我们用了状态管理
库,通过快照的方式
,在状态变更的过程中记录到数组
中,撤销
的时候通过索引
执向要显示的状态,重做
的时候也是通过索引
回复到之前状态状态。在继续操作
的时候,可能会删除
部分状态
定义保存记录的数据结构
// 撤销重做数据结构
undoRedoData: {
activeSnapshot: null, // 当前激活的快照数据
snapshots: [], // 存储的快照数据
current: -1, // 当前索引
}
更新快照数据
- 我们定义了操作的类型
type
, 分别是push
,undo
,redo
// 操作类型
export type UndoRedoActionType = {
type: 'push' | 'undo' | 'redo';
data: DatModelItem | null;
};
- 执行
push
操作代码内容为
if (type === 'push') {
// 深度拷贝要保存的记录
const newData = _.cloneDeep(data);
if (current === -1) {
newUndoRedoData.snapshots = [...snapshots, newData];
} else {
// 当前已经撤销,重新操作的时候要把某些记录取消
newUndoRedoData.snapshots = snapshots
.slice(0, current)
.concat([newData]);
}
// 重置当前激活的数据和索引
newUndoRedoData.activeSnapshot = null;
newUndoRedoData.current = -1;
}
注意:
由于我们用了flooks
状态库,它目前不支持不可变数据,所以我们在报存记录的时候用了深拷贝
,这里性能会有影响。如果不深拷贝,对象应用传递,会引发bug。之后我们会改造这个flooks,让他支持不可变数据。
- 执行
undo
操作代码内容为
if (type === 'undo') {
// 第一次执行撤销操作
if (current === -1) {
newUndoRedoData.current = snapshots.length - 1;
} else { // 连续执行撤销操作
newUndoRedoData.current = current - 1;
}
// 设置当前激活的快照数据
newUndoRedoData.activeSnapshot = snapshots[newUndoRedoData.current];
}
- 执行
redo
操作代码内容为
if (type === 'redo') {
// 可以执行重做操作
if (current != -1) {
newUndoRedoData.current = current + 1;
}
// 重做操作已经到最后一步,重置激活的状态和索引
if (current === snapshots.length - 1) {
newUndoRedoData.activeSnapshot = null;
newUndoRedoData.current = -1;
} else {
newUndoRedoData.activeSnapshot = snapshots[newUndoRedoData.current];
}
}
设置阈值,避免内存爆栈
我们一直是在往数组里添加记录,没有做上线判断,操作多的情况下可能会引发内存爆栈。设置阈值
来判断记录上线。代码如下
//阈值设置100,最多报错100次操作
let threshold = 100;
// 快照数据大于阈值
if (newUndoRedoData?.snapshots?.length > threshold) {
//保留最后的100条数据
newUndoRedoData.snapshots = newUndoRedoData.snapshots.splice(-threshold);
}
这里用了Array
的splice
方法,改方法负值
会从后向前截取。
页面逻辑
- 撤销回退按钮
<Tooltip placement="bottom" >
<Button
onClick={undo}
disabled={
undoRedoData.snapshots.length === 0 || undoRedoData.current === 0
}
icon={<UndoOutlined />}
/>
</Tooltip>
<Tooltip placement="bottom" >
<Button
disabled={undoRedoData.current === -1}
onClick={redo}
icon={<RedoOutlined />}
/>
</Tooltip>
这里主要注意下禁用的判断条件。
- 主页面渲染
const getJsx = () => {
// 有激活的快照数据说明有撤销或者重做的操作
const data = undoRedoData.activeSnapshot || nodes;
return data.map((item: DatModelItem) => {
return getJsxItem(item);
});
};
地址
- 演示地址
- 代码地址
交流沟通
建立了一个微信交流群,如需沟通讨论,请加入。
二维码过期,请添加微信号q1454763497
,备注image editor
,我会拉你进群
总结
以上我们实现了编辑器的撤销和重做,需要注意的是我们最好要用不可变数据,用深拷贝性能不好,最好用immer,后期我们会改造。功能部分大致代码介绍上面已经描述出来,如需要查看更详细的内容,请移步fast-image-editor。 大家觉得有帮忙,请在github帮忙star一下。
历史文章
- (开源)两个周末写了个图片编辑器
- (开源)给图片编辑器添加了辅助线
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!