使用react-konva制作在线制图应用(5)——撤销/重做(填坑篇)

1,797 阅读3分钟

这是我参与11月更文挑战的第5天,活动详情查看:2021最后一次更文挑战

相关阅读

  1. 使用konva制作在线photoshop(1)——元素拖拽、变形与导出
  2. 使用react-konva制作在线photoshop(2)——字体的文本与样式的修改
  3. 使用react-konva制作在线制图应用(3)——在线字体文件的动态渲染
  4. 使用react-konva制作在线制图应用(4)——撤销/重做(踩坑篇)

撤销/重做

上一篇文章中我们使用循环队列来保存每一步操作的数据。原理上是可行的,但在应用起来与react结合踩了不少的坑。接下来我们来填坑。

数据绑定

使用上一篇结尾的代码绑定到视图层后,发现当操作一步之后,再点击“撤回”无法撤回

最终效果如下 demo

这是因为视图层的 state 为 circularQueue 的一个实例,每次改变时,react 不能捕获到他的数据变更,因此我们必须把 current 绑定到 state 上,这里不详述了,这个系列结束后会给出源代码。

元素缩放时撤回/重做失效

正当我因为把撤回重做功能测试通过而暗自窃喜时,在拖动选择框大小时却发现,每次撤回时是按帧撤回的,而不是把最后一次缩放的操作放入循环列表中。进入Transformer中瞧瞧:

<Transformer
    ref={trRef}
    boundBoxFunc={(oldBox, newBox) => {
        // limit resize
        if (newBox.width < 5 || newBox.height < 5) {
            return oldBox
        }
        console.log('changedbox') // 打印一下
        handleInfo(newBox)
        return newBox
    }}
/>

发现控制台竟然打印了 100 多次“changedbox” Σ(っ °Д °;)っ

一想就知道boundBoxFunc是连续触发的,所以这里把每一帧的变化都传给了循环队列。本人那么多年经验的码农可不是吃 hello world 长大的!!(●'◡'●) 这里用防抖函数debounce包裹一下,

const debounceHandleInfo = debounce((newBox) => {
    trRef.current.forceUpdate()
    handleInfo(newBox)
}, 100)

const boundBoxFunc = (oldBox: any, newBox: any) => {
    if (newBox.width < 5 || newBox.height < 5) {
        return oldBox
    }
    debounceHandleInfo(newBox)
    return newBox
}

// ...

return <Transformer ref={trRef} boundBoxFunc={boundBoxFunc} />

运行: demo

woc!!(╯▔ 皿 ▔)╯ 为什么事情的发展和我想象中的不一样!Transformer 包裹的元素怎么会发生畸(ji)变?konva 内部一定是自己做了什么"见不得光"的事情,摔,去翻 api 文档...、

试了onDragEndisTransforming都差强人意,翻遍 github issue 和 stackoverflow 都没有,正当我绝望时,把鼠标挪到Transformer那行代码上,输了一个on,发现了神迹:onTransformEnd

<Transformer
    ref={trRef}
    boundBoxFunc={boundBoxFunc}
    onTransformEnd={(a) => {
        console.log('end', a)
    }}
/>

打印一下康康:

demo 发现内部target.attrs正是我们需要的包裹元素的属性!

赶紧给他传入循环队列中!

<Transformer
    // ...
    onTransformEnd={(a) => {
        handleInfo(a.target.attrs)
    }}
/>

demo wor! ε=( o ` ω′)ノ 图片倒是对了,怎么文本元素撤回时文本没了啊?

在逐层去看a.target.attrs中并没有什么奇奇怪怪的属性,但不清楚内部是不是会触发些什么 bug,我们只能把需要的属性择出来:

<Transformer
    // ...
    onTransformEnd={(a) => {
        const { scaleX, scaleY, rotation, skewX, skewY, x, y } = a.target.attrs
        handleInfo({ scaleX, scaleY, rotation, skewX, skewY, x, y })
    }}
/>

demo

大功告成!猜想估计是绑定了什么奇奇怪怪的属性,触发了内部隐藏的功能。

这部分终于结束了,明天更新处理图层相关的操作,敬请期待!