使用react-konva制作在线制图应用(4)——撤销/重做

2,280 阅读2分钟

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

相关阅读

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

撤销/重做

撤消重做需要我们借助循环队列来帮我们存储每一次操作的数据变更,并且只保存长度为 n 的数据,超过 N 步的操作会被自动删除。当然,最传统的头尾两个指针的循环队列不能满足要求,需要加另外一个指针 current 来表示当前的步数。

实现起来是比较复杂的,我们分步来实现。

入队、出队、判空、判满

由于我们在操作的时候,是一直不断往队列中添加操作数据,没有主动删除数据的操作,只有在队列已满时会覆盖之前的步数,所以这里不提供出队的接口。(这里的删除并不是“回退”操作,回退是保留原数据,指针往后挪了一位,进行“重做”操作时指针会前移)。

class circularQueue {
    constructor(size) {
        this.length = size
        this.front = 0
        this.tail = 0
        this.i = 0
        this.list = new Array(size)
    }

    // 入队
    enqueue(item) {
        if (this.isFull()) {
            // 满了移动头指针
            this.front = (this.front + 1) % this.length
        }
        const index = this.tail % this.length
        this.list[index] = item
        this.tail = (index + 1) % this.length
    }

    // 不涉及
    dequeue() {}

    isEmpty = () => {
        return (
            this.front === this.tail &&
            typeof this.list[this.front] === 'undefined'
        )
    }

    isFull = () => {
        return (
            this.front === this.tail &&
            typeof this.list[this.front] !== 'undefined'
        )
    } // 队列满了

    print() {
        let i = 0
        let p = this.front
        while (i < this.length) {
            console.log(this.list[p])
            p = (p + 1) % this.length
            i++
        }
    }
}

写个简单示例测试下:

const queue = new circularQ(3)

queue.enqueue(0)
queue.enqueue(1)
queue.enqueue(2)
queue.enqueue(3)
queue.enqueue(4)

queue.print()

输出

2
3
4

第三根指针————current

第三根指针 current 的用处是,用户进行回退/重做操作时,当前页面上所显示的操作的状态。但当 current 不为尾指针前驱时,在进行入队操作,就要把从 current 到队尾指针之间的所有元素清空,并且重置尾指针,

在移动指针的时候,要注意越界的问题,current 指针后移不能越过 tail、前移不能越过 front。

以下为循环队列的全部代码:

class circularQueue {
    constructor(size) {
        this.length = size
        this.front = 0
        this.tail = 0
        this.current = 0
        this.list = new Array(size)
    }

    get canMoveForward() {
        // 能否后移
        return (this.current + 1) % this.length !== this.tail
    }
    get canMoveBack() {
        // current能否回退
        return this.current !== this.front
    }

    clearAfterCurrent() {
        let i = this.current
        const length = this.length

        while ((i + 1) % length !== this.tail) {
            const clearIndex = (i + 1) % length
            this.list[clearIndex] = undefined
            i = clearIndex
        }
        this.tail = (this.current + 1) % this.length
    }

    // 入队
    enqueue(item) {
        // 当入队时current不是处于队尾指针的前驱时,需要清空current到队尾之间的所有元素,并重置尾指针
        if (this.isFull() && (this.current + 1) % this.length !== this.tail) {
            this.clearAfterCurrent()
        }

        if (this.isFull()) {
            this.tail = (this.current + 1) % this.length
            // 满了移动头指针
            this.front = (this.front + 1) % this.length
        }
        this.list[this.tail] = item
        this.current = this.tail
        this.tail = (this.tail + 1) % this.length
    }

    // 不涉及
    dequeue() {}

    isEmpty = () => {
        return typeof this.list[this.front] === 'undefined'
    }

    isFull = () => {
        return (
            this.front === this.tail &&
            typeof this.list[this.front] !== 'undefined'
        )
    } // 队列满了

    getCurrent() {
        console.log('getCurrent', this.list[this.current])
        return this.list[this.current]
    }

    // 往右移一步 (尾指针方向)
    moveForward() {
        if (this.canMoveForward) {
            this.current = this.isFull()
                ? (this.current - 1 + this.length) % this.length
                : this.current + 1
        }
    }
    // 往左移一步 (头指针方向)
    moveBack() {
        if (this.canMoveBack) {
            this.current = this.isFull()
                ? (this.current - 1 + this.length) % this.length
                : this.current - 1
        }
    }

    print() {
        let i = 0
        let p = this.front
        while (i < this.length) {
            console.log(this.list[p])
            p = (p + 1) % this.length
            i++
        }
    }
}

我们把 circularQueue 与画布上的 infos 绑定起来,最终效果如下:

效果