本文已参与掘金创作者训练营第三期,详情查看:掘力计划|创作者训练营第三期正在进行,「写」出个人影响力。
前言
上篇文章我们实现了图片编辑器的辅助线,大多数的工具类型软件都支持撤销和重做,趁热打铁,我们图片编辑器的撤销重做的功能给实现一下,丰富我们的图片编辑器的功能。
演示
演示地址
g2lfkxyqlyvs.jpg)
实现流程
原理讲解
通过上面的演示我们分析有这三种类型的操作,分别是push,undo,redo
1. 在操作的过程中记录状态push
通过流程图我们可以看到,正常的操作直接记录状态就可以。
2.在操作过程中撤销操作undo
通过流程图我们可以看到在撤销1操作后,当前的状态会到操作2的状态。在进一步点击撤销2会回到操作1的状态
3.在操作过程中执行重做操作rudo
通过流程图我们可以看到在操作重做1后,图片会回到操作2的状态。
4.继续在操作的过程中记录状态again push
gd5y123lejpm.jpg)
注意:此时我们可以有两种操作,一是继续点击重做,图片会回到操作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);
    });
};
地址
- 演示地址
 - 代码地址
 
交流沟通
建立了一个微信交流群,如需沟通讨论,请加入。
gq2s5qnvt5k1.jpg)
二维码过期,请添加微信号q1454763497,备注image editor,我会拉你进群
总结
以上我们实现了编辑器的撤销和重做,需要注意的是我们最好要用不可变数据,用深拷贝性能不好,最好用immer,后期我们会改造。功能部分大致代码介绍上面已经描述出来,如需要查看更详细的内容,请移步fast-image-editor。 大家觉得有帮忙,请在github帮忙star一下。
历史文章
- (开源)两个周末写了个图片编辑器
 - (开源)给图片编辑器添加了辅助线
 
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
 - 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
 
- 提示下载完但解压或打开不了?
 
- 找不到素材资源介绍文章里的示例图片?
 
- 模板不会安装或需要功能定制以及二次开发?
 
                    
    
发表评论
还没有评论,快来抢沙发吧!