日期:2021年6月21日标签:MiniCode

react实现简易画板程序 #

在这之前,我写过一个vue版本的画板程序。最近因为 minicode的上线,就把画板用 react 重写了,其实思路什么的都是一样的,只是使用的前端框架不一样罢了。

react-paint地址

github源码地址: https://github.com/pengfeiw/react-paint

一. react-paint #

程序界面如图,上方是工具栏,下面是画布绘制区域。

react画图程序

主要功能使用html canvas实现,可以在 MDN canvas教程上学习 canvas 的二维图形绘制。

下面我说下部分的主要功能实现,你们可以在github上找我的源码进行参考。

二. 功能实现 #

笔、橡皮擦和形状功能 #

主要是监听画板的鼠标事件实现的,这里我运用了面向对象的思想,写了一个Tool类,Pen、Shape、Eraser都是通过继承实现的。

class Tool {
    public onMouseDown(event: MouseEvent): void {
        //
    }

    public onMouseMove(event: MouseEvent): void {
        //
    }

    public onMouseUp(event: MouseEvent): void {
        //
    }
}

class Pen extends Tool {
    // 覆盖基类的三个鼠标事件方法
    public onMouseDown(event: MouseEvent): void {
        //
    }

    public onMouseMove(event: MouseEvent): void {
        //
    }

    public onMouseUp(event: MouseEvent): void {
        //
    }
}

class Shape extends Tool {
    ...
}

// 由于橡皮擦和笔的实现基本一样,仅颜色和线宽不一样,所以我直接将Eraser继承Pen
class Eraser extends Pen {
    ...
}

从mousedown至mouseup这一个完整的过程,表示一段线段或者一个形状的绘制。具体实现可以看我的源码。

填充功能 #

填充功能使用了图形学的flood fill算法,具体思路可以参考我的这篇文章高效率的种子填充算法。这个功能我花的时间是最多的,在算法上我花了很多时间去查阅资料和实践。

回退和前进功能 #

这个主要利用了双栈的思想,将每一步canvas的剪影(ImageData)存储在栈中。每次在canvas上绘制一个形状或者线段,又或者是擦除一条线段,都将此时的canvas图片(ImageData)存储在imageData1栈顶。back时将imageData1的栈顶元素弹栈,并将该元素push到imageData2中。forward的时候,如果imageData2非空,将imageData2执行弹栈操作,将该元素push到imageData1中。每次操作后,都将imageData1的栈顶ImageData加载到canvas上,即实现了撤销和回退功能。

class Snapshot {
    private imageData1: ImageData[] = [];
    private imageData2: ImageData[] = [];
    public add(imageData: ImageData) {
        this.imageData1.push(imageData);
    }
    public back() {
        if (this.imageData1.length > 1) {
            const imageData = this.imageData1.pop() as ImageData;
            this.imageData2.push(imageData);
        }
        
        return this.imageData1.length > 0 ? this.imageData1[this.imageData1.length - 1] : null;
    }

    public forward() {
        if (this.imageData2.length > 0) {
            const imageData = this.imageData2.pop() as ImageData;
            this.imageData1.push(imageData);
        }
        return this.imageData1.length > 0 ? this.imageData1[this.imageData1.length - 1] : null;
    }
}

其他功能 #

其他功能相对比较简单,就是更改一些线的样式,这里就不再详述了,更多细节请参考我的github。

如果你对该程序有疑问,可以在我的个人网站文章下方留言,也可以留下您的联系方式☺。

(完)

目录