React Canvas按右键无法移动方块问题求助
问题分析与解决方案
核心问题点
- 闭包陷阱:
animate、handleKeyDown、handleKeyUp等函数的依赖项不完整,导致它们始终引用组件初始渲染时的状态值,无法响应后续状态更新。 - 状态直接修改:代码中存在
velocity.y += gravity、position.y += velocity.y这类直接修改状态对象的操作,违反React状态不可变原则,会导致状态更新不被检测到。 - 动画逻辑拆分混乱:
animate和update函数中都在修改velocity状态,导致状态更新冲突,左右移动的逻辑被重力更新覆盖。 - 键盘事件焦点问题:
canvas的tabIndex=-1会导致难以获取焦点,无法触发键盘事件。
修正后的代码
import { useRef, useState, useCallback, useEffect } from 'react'; const gravity = 4.5; const Canvas = () => { const { innerWidth: innerwidth, innerHeight: innerheight } = window; const canvasRef = useRef(); const [position, setPosition] = useState({ x: 100, y: 100 }); const [size, setSize] = useState({ width: 30, height: 30 }); const [velocity, setVelocity] = useState({ x: 0, y: 0 }); const [pressRight, setPressRight] = useState(false); const [pressLeft, setPressLeft] = useState(false); const draw = useCallback((context) => { context.fillStyle = 'red'; context.fillRect(position.x, position.y, size.width, size.height); }, [position, size]); const update = useCallback((canvas) => { // 处理左右移动速度 let newXVelocity = 0; if (pressRight) newXVelocity = 5; if (pressLeft) newXVelocity = -5; // 处理重力与落地 let newYVelocity = velocity.y; if (position.y + size.height < canvas.height) { newYVelocity += gravity; } else { newYVelocity = 0; // 落地时修正位置,防止方块超出画布 setPosition(prev => ({ ...prev, y: canvas.height - size.height })); return; } // 更新位置(使用函数式更新获取最新状态) setPosition(prev => ({ x: prev.x + newXVelocity, y: prev.y + newYVelocity })); setVelocity({ x: newXVelocity, y: newYVelocity }); }, [position, size, velocity, pressRight, pressLeft]); const animate = useCallback((context, width, height, canvas) => { requestAnimationFrame(() => animate(context, width, height, canvas)); context.clearRect(0, 0, width, height); update(canvas); draw(context); }, [update, draw]); useEffect(() => { const canvas = canvasRef.current; const context = canvas.getContext('2d'); canvas.width = innerwidth - 10; canvas.height = innerheight - 10; // 让canvas获取焦点,确保键盘事件触发 canvas.focus(); animate(context, canvas.width, canvas.height, canvas); }, [animate]); const handleKeyDown = useCallback(({ key }) => { switch (key) { case 'ArrowLeft': setPressLeft(true); break; case 'ArrowRight': setPressRight(true); break; case ' ': // Spacebar setVelocity(prev => ({ ...prev, y: prev.y - 50 })); break; default: console.log(`Unknown key: ${key}`); } }, []); const handleKeyUp = useCallback(({ key }) => { switch (key) { case 'ArrowLeft': setPressLeft(false); break; case 'ArrowRight': setPressRight(false); break; case ' ': break; default: console.log(`Unknown key: ${key}`); } }, []); return ( <canvas ref={canvasRef} tabIndex={0} onKeyDown={handleKeyDown} onKeyUp={handleKeyUp} style={{ border: '1px solid #000' }} // 可选:添加边框方便识别canvas区域 /> ); }; export default Canvas;
关键修改说明
- 修复闭包问题:给所有
useCallback和useEffect添加完整的依赖项,确保函数能获取最新状态。 - 状态不可变更新:使用函数式更新(
setState(prev => ...))和新对象替换的方式修改状态,避免直接修改原对象。 - 统一动画逻辑:将所有状态更新逻辑集中到
update函数中,避免多函数修改状态导致的冲突。 - 优化键盘事件:将
tabIndex设为0,在useEffect中主动调用canvas.focus()确保能触发键盘事件,同时补全了左方向键的处理逻辑。
内容的提问来源于stack exchange,提问作者tomtom
相关产品推荐
相关产品推荐

