tomtom
tomtom

Reputation: 99

No response on keydown when using requestAnimationFrame in React.js

I have been trying to create a little animation on canvas using React.js.

I expect the red block inside the canvas to go up and drop back down when I hit my spacebar by decreasing the velocity so that it will execute the if statement I have got and pull it back down, but so far it's not working. I can see the velocity value did change and it did execute the if statement when I console.log, it doesn't seem like showing on the animation.

const { useRef, useEffect, useState } = React;

const Canvas = () => {
  const { innerWidth: innerwidth, innerHeight: innerheight } = window;
  const contextRef = 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 gravity = 4.5

  const draw = (context) => {
    context.fillStyle = 'red'
    context.fillRect(position.x, position.y, size.width, size.height);
  }; 

  const update = (context, canvas) => {
    draw(context)
    setPosition({ x:position.x, y:position.y += velocity.y })

    if (position.y + size.height +velocity.y <= canvas.height) {
      setVelocity({ x:velocity.x, y: velocity.y += gravity })
    } else {
      setVelocity({ x:velocity.x, y: velocity.y = 0 })
    }
  }

  const animate = (context, width, height, canvas) => {
    requestAnimationFrame(() => {
      animate(context, width, height, canvas )
    })
    context.clearRect(0, 0, width, height) 
    update(context, canvas)
  } 

  useEffect(() => {
    const canvas = contextRef.current;
    const context = canvas.getContext("2d");
    canvas.width = innerwidth - 2
    canvas.height = innerheight - 2
    animate(context, canvas.width, canvas.height, canvas)
  }, []);


const handleKeydown = (e) => {
  switch(e.keyCode) {
    case 37:
      console.log(velocity)
      return "left";
    case 39:
      return "right";
    case 32:   
      setVelocity({ x:velocity.x, y:velocity.y -= 50 });
      console.log(velocity)
      break
    default:
      console.log("keyCode is " + e.keyCode)
      return 'default';
    }    
  }

  return (
    <canvas ref={contextRef} tabIndex={-1} onKeyDown={(e) => {
      handleKeydown(e)
    }} />
  );
};

const App = () => <Canvas />;

ReactDOM
  .createRoot(document.getElementById("root"))
  .render(<App />);
html, body { width: 100%; height: 100%; margin: 0; }
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>

What can I try next?

Upvotes: 2

Views: 91

Answers (1)

Mr. Polywhirl
Mr. Polywhirl

Reputation: 48638

You should wrap some of your function in a useCallback so that they are not redefined on each render.

Also, I would use key instead of keyCode.

const { useCallback, useEffect, useState, useRef } = 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 draw = useCallback((context) => {
    context.fillStyle = 'red';
    context.fillRect(position.x, position.y, size.width, size.height);
  }, [position, size]); 

  const update = useCallback((context, canvas) => {
    draw(context)
    setPosition({ x: position.x, y: position.y += velocity.y })

    if (position.y + size.height + velocity.y <= canvas.height) {
      setVelocity({ x: velocity.x, y: velocity.y += gravity })
    } else {
      setVelocity({ x:velocity.x, y: velocity.y = 0 })
    }
  }, [position, size, velocity]);

  const animate = (context, width, height, canvas) => {
    requestAnimationFrame(() => {
      animate(context, width, height, canvas);
    });
    context.clearRect(0, 0, width, height);
    update(context, canvas);
  };

  useEffect(() => {
    const canvas = canvasRef.current;
    const context = canvas.getContext('2d');
    canvas.width= innerwidth - 10;
    canvas.height= innerheight - 10;
    animate(context, canvas.width, canvas.height, canvas)
  }, []);


const handleKeyDown = useCallback(({ key }) => {
  switch (key) {
    case 'ArrowLeft':
      break;
    case 'ArrowRight':
      break;
    case ' ': // Spacebar
      setVelocity({ x: velocity.x, y: velocity.y -= 50 });
      console.log(velocity)
      break
    default:
      console.log(`Unknown key: ${key}`);
    }    
  }, []);

  return (
    <canvas
      ref={canvasRef}
      tabIndex={-1}
      onKeyDown={handleKeyDown}
    />
  );
};

const App = () => <Canvas />;

ReactDOM
  .createRoot(document.getElementById("root"))
  .render(<App />);
html, body, #root { width: 100%; height: 100%; margin: 0; }
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>

Upvotes: 1

Related Questions