V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
React
weimo383
V2EX  ›  React

如何用 react hooks 封装一个鼠标绘图组件?

  •  
  •   weimo383 · 53 天前 · 1466 次点击
    这是一个创建于 53 天前的主题,其中的信息可能已经有所发展或是发生改变。

    这是我想要封装的一个鼠标画图组件,不停的触发 mouseMove 获取当前的位置,同时又缓存了上一次的位置,连接两个点即可,但是我想要用 hooks 封装,感觉很难,没有头绪。

    // When true, moving the mouse draws on the canvas
    let isDrawing = false;
    let x = 0;
    let y = 0;
    
    const myPics = document.getElementById('myPics');
    const context = myPics.getContext('2d');
    
    // event.offsetX, event.offsetY gives the (x,y) offset from the edge of the canvas.
    
    // Add the event listeners for mousedown, mousemove, and mouseup
    myPics.addEventListener('mousedown', e => {
      x = e.offsetX;
      y = e.offsetY;
      isDrawing = true;
    });
    
    myPics.addEventListener('mousemove', e => {
      if (isDrawing === true) {
        drawLine(context, x, y, e.offsetX, e.offsetY);
        x = e.offsetX;
        y = e.offsetY;
      }
    });
    
    window.addEventListener('mouseup', e => {
      if (isDrawing === true) {
        drawLine(context, x, y, e.offsetX, e.offsetY);
        x = 0;
        y = 0;
        isDrawing = false;
      }
    });
    
    function drawLine(context, x1, y1, x2, y2) {
      context.beginPath();
      context.strokeStyle = 'black';
      context.lineWidth = 1;
      context.moveTo(x1, y1);
      context.lineTo(x2, y2);
      context.stroke();
      context.closePath();
    }
    

    这是我写出来的完全不能画图的 hook (我估计是因为没有像上面那样获取上次位置和当前位置的原因

    const Canvas = () => {
        const canvasRef = React.useRef(null);
        //const [paths, setPaths] = useState([]);
        const [isDrawing, setIsDrawing] = useState(false);
        const [pos, setPos] = useState({
            x: 0,
            y: 0,
        });
        const [isMouseDown, setIsMouseDown] = useState(false);
        const [isMouseMove, setIsMouseMove] = useState(false);
        const [isMouseUp, setIsMouseUp] = useState(false);
        useEffect(() => {
            const ctx = canvasRef.current.getContext("2d");
            if (isDrawing) {
                console.log("isDrawing");
                ctx.beginPath();
                ctx.strokeStyle = 'black';
                ctx.lineWidth = 1;
                ctx.lineTo(pos.x, pos.y);
                ctx.stroke();
                ctx.closePath();
            }
        }, [isMouseDown, isDrawing, pos]);
        const handleMouseDown = (e) => {
            setPos({
                x: e.clientX,
                y: e.clientY,
            });
            setIsDrawing(true);
            setIsMouseDown(true);
        }
    
        const handleMouseUp = () => {
            setIsDrawing(false);
            setIsMouseUp(false);
        }
        const handleMouseMove = (e) => {
            // console.log(e);
            setIsMouseMove(true);
            setPos({
                x: e.clientX,
                y: e.clientY,
            });
            //console.log(state.ctx);
        }
        return (
            <div className="back-div" >
                <canvas ref={canvasRef} id="canvas1" className="paint-canvas" onMouseDown={handleMouseDown} onMouseMove={handleMouseMove} onMouseUp={handleMouseUp}>
                </canvas>
            </div>
        )
    }
    
    18 条回复    2020-11-30 20:26:36 +08:00
    weimo383
        1
    weimo383   53 天前
    顺带一提,我的 e.offsetX 为什么读出来是 undefined
    weimo383
        2
    weimo383   53 天前 via Android
    啊,别沉啊
    frankwei777
        3
    frankwei777   53 天前
    react 的 offsetX 在 event.nativeEvent 中
    shenyu1996
        4
    shenyu1996   53 天前
    https://codesandbox.io/s/busy-saha-rs7f9
    稍稍改了一下 可以了
    weimo383
        5
    weimo383   53 天前 via Android
    @frankwei777 谢谢
    weimo383
        6
    weimo383   53 天前 via Android
    @shenyu1996 谢谢!
    v135ex
        7
    v135ex   53 天前
    有啥难的,自己多想想就写出来了
    weimo383
        8
    weimo383   53 天前
    @v135ex
    还是有点难的,你可以试试,css 改动 canvas 大小的话鼠标位置和 canvas 上绘图坐标是不一致的
    IamJ
        9
    IamJ   53 天前 via iPhone   ❤️ 1
    @weimo383 用标签属性设置大小而不是样式
    v135ex
        10
    v135ex   52 天前
    @weimo383 好的 没有尝试过妄下定论了,我也尝试下学习一下
    xrr2016
        11
    xrr2016   52 天前
    我的建议是用成熟的绘图库,例如 p5.js 这种,然后在用 hooks 做你需要的功能
    weimo383
        12
    weimo383   52 天前 via Android
    @xrr2016 主要是还是不想依赖外部库(增加负担)
    funnyecho
        13
    funnyecho   50 天前   ❤️ 1
    个人看法,这里用 react hook ( useState )其实没有什么意义,你第一段代码都自成一体了,直接使用事件驱动来的更快,不需要额外等待 react setState 的调度。

    与其说使用 hook,不如把第一段代码封装成一个纯函数,接受一个 ref 来处理绘图,在外部 FC 中调用这个函数来的更好。
    weimo383
        14
    weimo383   50 天前 via Android
    @funnyecho ref 可以在组件中传递吗?
    大佬能不能再具体一些哈
    funnyecho
        15
    funnyecho   50 天前
    @weimo383

    把第一段代码封装成函数:
    ```
    // 函数接受一个 canvas element,并返回 destructor 函数用来 removeListener
    type draw = ($canvas) => (() => void)
    ```

    然后,React.FC 中调用 draw 方法,举个例子:
    ```
    useEffect(() => {

    return draw(canvasRef.current)
    }, [canvasRef.current])
    ```
    weimo383
        16
    weimo383   50 天前
    @funnyecho 我的组件得要能够保存绘制历史记录,所以副作用和状态是相关的
    funnyecho
        17
    funnyecho   50 天前 via iPhone
    @weimo383 看代码就几个状态,每次 mousedown 都会重置,保存在 draw 函数内部的作用域就好了呀。
    weimo383
        18
    weimo383   50 天前
    @funnyecho 后续会有返回键按钮,保存在 draw 内部的作用域时怎么返回到上一步,还是保存为内部 state 吧
    关于   ·   帮助文档   ·   FAQ   ·   API   ·   我们的愿景   ·   广告投放   ·   感谢   ·   实用小工具   ·   2026 人在线   最高记录 5497   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 20ms · UTC 00:09 · PVG 08:09 · LAX 16:09 · JFK 19:09
    ♥ Do have faith in what you're doing.