Last active
September 19, 2024 21:49
-
-
Save adriancmiranda/e859a902bee91e5f3229cb894972ba9a to your computer and use it in GitHub Desktop.
Hook for canvas rendering in react | useCanvas.js | useWindowSize.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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} | |
/> | |
); | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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
.