react实现简易画板程序

react实现简易画板程序

文章同时发布于:https://pengfeixc.com/blog/60d073bce97367196dce3efc
在这之前,我写过一个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。

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

(完)

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是使用 React 实现一个简易的三级菜单的示例代码: ```jsx import React, { useState } from 'react'; const MenuItem = ({ title, subMenu }) => { const [isOpen, setIsOpen] = useState(false); const toggleSubMenu = () => { setIsOpen(!isOpen); }; return ( <li> <div onClick={toggleSubMenu}>{title}</div> {isOpen && ( <ul> {subMenu.map((item, index) => ( <MenuItem key={index} title={item.title} subMenu={item.subMenu} /> ))} </ul> )} </li> ); }; const Menu = () => { const menuData = [ { title: 'Menu 1', subMenu: [ { title: 'Submenu 1-1', subMenu: [ { title: 'Sub-submenu 1-1-1', subMenu: [] }, { title: 'Sub-submenu 1-1-2', subMenu: [] } ] }, { title: 'Submenu 1-2', subMenu: [] } ] }, { title: 'Menu 2', subMenu: [] }, { title: 'Menu 3', subMenu: [] } ]; return ( <ul> {menuData.map((item, index) => ( <MenuItem key={index} title={item.title} subMenu={item.subMenu} /> ))} </ul> ); }; export default Menu; ``` 在上述代码中,我们定义了两个组件:`MenuItem` 和 `Menu`。`MenuItem` 组件负责渲染每个菜单项,包括标题和可能的子菜单。通过使用 `useState` 来管理子菜单的展开与折叠状态。当点击菜单项时,`toggleSubMenu` 函数会切换子菜单的展开状态。 `Menu` 组件作为顶级组件,负责渲染整个菜单。在 `Menu` 组件中,我们传入一个包含菜单数据的数组 `menuData`,并通过 `map` 方法来遍历生成每个菜单项的 `MenuItem` 组件。 通过嵌套使用 `MenuItem` 组件,我们可以实现多级嵌套的菜单结构。每个菜单项都可以包含一个子菜单,从而实现了三级菜单的效果。 注意:上述代码只是一个简易的示例,实际应用中可能需要根据需求进行修改和扩展。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值