Skip to content

Instantly share code, notes, and snippets.

@dcollien
Created March 20, 2024 01:23
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dcollien/c80ba648e664a07515a4fe5b767af24b to your computer and use it in GitHub Desktop.
Save dcollien/c80ba648e664a07515a4fe5b767af24b to your computer and use it in GitHub Desktop.
React animated canvas component
import React from 'react';
export default function useAnimationFrame(callback: (dt: number) => void, isRunning = false): [() => void, () => void] {
const lastStepRef = React.useRef<number | null>(null);
const frameRequestRef = React.useRef<number | null>(null);
const savedCallback = React.useRef(callback);
const step = () => {
const now = Date.now();
const dt = (now - lastStepRef.current!) / 1000;
lastStepRef.current = now;
savedCallback.current(dt);
frameRequestRef.current = window.requestAnimationFrame(step);
};
const stop = () => {
if (frameRequestRef.current) {
window.cancelAnimationFrame(frameRequestRef.current);
frameRequestRef.current = null;
}
};
const start = () => {
if (!frameRequestRef.current) {
lastStepRef.current = Date.now();
frameRequestRef.current = window.requestAnimationFrame(step);
}
}
React.useEffect(() => {
savedCallback.current = callback;
}, [callback]);
React.useEffect(() => {
if (isRunning) {
start();
} else {
stop();
}
}, [isRunning]);
React.useEffect(() => {
return () => stop();
}, []);
return [start, stop];
};
import React, {
useRef,
useImperativeHandle
} from "react";
import useAnimationFrame from "../hooks/animationFrame";
type UpdateHandler = (dt: number, canvas: HTMLCanvasElement) => void;
export interface CanvasProps extends React.CanvasHTMLAttributes<HTMLCanvasElement> {
onAnimationFrame: UpdateHandler;
isAnimating?: boolean;
}
export const Canvas = React.forwardRef<HTMLCanvasElement, CanvasProps>(({
onAnimationFrame,
isAnimating = true,
...props
}, ref) => {
const canvasRef = useRef<HTMLCanvasElement>(null);
useImperativeHandle(ref, () => canvasRef.current as HTMLCanvasElement);
useAnimationFrame((dt) => {
onAnimationFrame(dt, canvasRef.current as HTMLCanvasElement);
}, isAnimating);
return (
<canvas ref={canvasRef} {...props}></canvas>
);
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment