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

2020-11-27 11:39:34 +08:00
 weimo383

这是我想要封装的一个鼠标画图组件,不停的触发 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>
    )
}
2660 次点击
所在节点    React
18 条回复
weimo383
2020-11-27 11:42:11 +08:00
顺带一提,我的 e.offsetX 为什么读出来是 undefined
weimo383
2020-11-27 14:30:29 +08:00
啊,别沉啊
frankwei777
2020-11-27 14:48:59 +08:00
react 的 offsetX 在 event.nativeEvent 中
shenyu1996
2020-11-27 15:27:32 +08:00
https://codesandbox.io/s/busy-saha-rs7f9
稍稍改了一下 可以了
weimo383
2020-11-27 15:39:31 +08:00
@frankwei777 谢谢
weimo383
2020-11-27 15:39:39 +08:00
@shenyu1996 谢谢!
BUHeF254Lpd1MH06
2020-11-27 15:55:52 +08:00
有啥难的,自己多想想就写出来了
weimo383
2020-11-27 18:57:16 +08:00
@v135ex
还是有点难的,你可以试试,css 改动 canvas 大小的话鼠标位置和 canvas 上绘图坐标是不一致的
IamJ
2020-11-27 19:25:56 +08:00
@weimo383 用标签属性设置大小而不是样式
BUHeF254Lpd1MH06
2020-11-28 10:28:07 +08:00
@weimo383 好的 没有尝试过妄下定论了,我也尝试下学习一下
xrr2016
2020-11-28 14:54:19 +08:00
我的建议是用成熟的绘图库,例如 p5.js 这种,然后在用 hooks 做你需要的功能
weimo383
2020-11-28 18:03:51 +08:00
@xrr2016 主要是还是不想依赖外部库(增加负担)
funnyecho
2020-11-30 10:23:59 +08:00
个人看法,这里用 react hook ( useState )其实没有什么意义,你第一段代码都自成一体了,直接使用事件驱动来的更快,不需要额外等待 react setState 的调度。

与其说使用 hook,不如把第一段代码封装成一个纯函数,接受一个 ref 来处理绘图,在外部 FC 中调用这个函数来的更好。
weimo383
2020-11-30 11:38:23 +08:00
@funnyecho ref 可以在组件中传递吗?
大佬能不能再具体一些哈
funnyecho
2020-11-30 14:43:20 +08:00
@weimo383

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

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

return draw(canvasRef.current)
}, [canvasRef.current])
```
weimo383
2020-11-30 18:16:33 +08:00
@funnyecho 我的组件得要能够保存绘制历史记录,所以副作用和状态是相关的
funnyecho
2020-11-30 19:07:55 +08:00
@weimo383 看代码就几个状态,每次 mousedown 都会重置,保存在 draw 函数内部的作用域就好了呀。
weimo383
2020-11-30 20:26:36 +08:00
@funnyecho 后续会有返回键按钮,保存在 draw 内部的作用域时怎么返回到上一步,还是保存为内部 state 吧

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/729801

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX