Skip to content

Instantly share code, notes, and snippets.

@adriancmiranda
Last active September 19, 2024 21:49
Show Gist options
  • Save adriancmiranda/e859a902bee91e5f3229cb894972ba9a to your computer and use it in GitHub Desktop.
Save adriancmiranda/e859a902bee91e5f3229cb894972ba9a to your computer and use it in GitHub Desktop.
Hook for canvas rendering in react | useCanvas.js | useWindowSize.js
import React from 'react';
import useCanvas from './useCanvas';
import useWindowSize from './useWindowSize';
const Canvas = (props) => {
const [canvasRef, tracer] = useCanvas('2d');
const [width, height] = useWindowSize();
const draw = tracer((gl) => {
gl.clearRect(0, 0, width, height);
// ...
});
React.useEffect(() => draw(), [width, height]);
return (
<canvas
width={width}
height={height}
{...props}
ref={canvasRef}
/>
);
};
import React from 'react';
export const requestAnimationFrame = (
window.requestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.msRequestAnimationFrame
);
export const cancelAnimationFrame = (
window.cancelAnimationFrame ||
window.mozCancelAnimationFrame
);
export const useCanvas = (contextType = '2d', contextAttributes = undefined) => {
const canvasRef = React.useRef(null);
let canvas, context, animationFrameId, tracer;
const updateContext = () => {
canvas = canvasRef.current;
if (canvas) {
context = canvas.getContext(contextType, contextAttributes);
animationFrameId = requestAnimationFrame(renderFrame);
}
};
const setTracer = (tracerFn) => {
tracer = tracerFn;
return updateContext;
};
function renderFrame() {
animationFrameId = requestAnimationFrame(renderFrame);
tracer(context, canvas);
}
React.useEffect(() => {
updateContext();
return () => cancelAnimationFrame(animationFrameId);
}, []);
return [canvasRef, setTracer];
};
export default useCanvas;
import React from 'react';
export const useWindowSize = (pixelRatio = 1) => {
const ratio = Math.round(pixelRatio) || 1;
const [size, setSize] = React.useState([window.innerWidth * ratio, window.innerHeight * ratio]);
React.useLayoutEffect(() => {
const updateSize = () => setSize([window.innerWidth * ratio, window.innerHeight * ratio]);
window.addEventListener('resize', updateSize);
updateSize();
return () => window.removeEventListener('resize', updateSize);
}, []);
return size;
};
export default useWindowSize;
@jonjaques
Copy link

jonjaques commented Sep 19, 2024

In my usecase, I did not want the canvas to continuously re-render, so I changed the renderFrame fn to say the following, instead of calling itself recursively.

Great snippet btw, thank you! Handles all of the edge cases very well, as well as "retina" displays, if you pair it with window.devicePixelRatio.

	function renderFrame() {
		animationFrameId = requestAnimationFrame(() => tracer(context, canvas));
	}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment