Created
December 5, 2020 00:37
-
-
Save Flaque/e56898ddcabd3d6bfd1d46533e19c518 to your computer and use it in GitHub Desktop.
This file contains hidden or 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 { | |
DetailedHTMLProps, | |
CanvasHTMLAttributes, | |
useLayoutEffect, | |
useEffect, | |
useRef, | |
createContext, | |
useContext, | |
Ref, | |
MutableRefObject, | |
useState, | |
} from "react"; | |
import lerp from "lerp"; | |
const CanvasReactContext = createContext<{ | |
subscribe: (ref: Ref<any>) => any; | |
}>({ | |
subscribe: () => {}, | |
}); | |
type FrameFn = (ctx: CanvasRenderingContext2D, delta: number) => void; | |
function useAnimationFrame(render: (delta: number) => any) { | |
// Use useRef for mutable variables that we want to persist | |
// without triggering a re-render on their change | |
const requestRef = useRef<any>(); | |
const previousTimeRef = useRef<any>(); | |
const animate = (time) => { | |
if (previousTimeRef.current != undefined) { | |
const deltaTime = time - previousTimeRef.current; | |
// Pass on a function to the setter of the state | |
// to make sure we always have the latest state | |
render(deltaTime); | |
} | |
previousTimeRef.current = time; | |
requestRef.current = requestAnimationFrame(animate); | |
}; | |
useEffect(() => { | |
requestRef.current = requestAnimationFrame(animate); | |
return () => cancelAnimationFrame(requestRef.current); | |
}, []); // Make sure the effect runs only once | |
} | |
export function useFrame(fn: FrameFn) { | |
const canvas = useContext(CanvasReactContext); | |
const ref = useRef<FrameFn>(fn); | |
useLayoutEffect(() => void (ref.current = fn), [fn]); | |
useEffect(() => { | |
const unsubscribe = canvas.subscribe(ref); | |
return () => unsubscribe(); | |
}, [fn]); | |
} | |
export function useLerp(oldValue: number) { | |
const ref = useRef<number>(oldValue); | |
function next(newValue: number, alpha: number) { | |
ref.current = lerp(ref.current, newValue, alpha); | |
return ref.current; | |
} | |
return next; | |
} | |
export function Canvas( | |
props: DetailedHTMLProps< | |
CanvasHTMLAttributes<HTMLCanvasElement>, | |
HTMLCanvasElement | |
> | |
) { | |
const canvasRef = useRef<HTMLCanvasElement>(); | |
const internal = useRef<{ | |
subscribers: Array<MutableRefObject<FrameFn>>; | |
}>({ | |
subscribers: [], | |
}); | |
function renderFrame(delta: number) { | |
const ctx = canvasRef.current.getContext("2d") as CanvasRenderingContext2D; | |
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); | |
for (let fn of internal.current.subscribers) { | |
fn.current(ctx, delta); | |
} | |
} | |
function subscribe(ref: MutableRefObject<FrameFn>) { | |
internal.current.subscribers.push(ref); | |
return () => { | |
if (internal.current?.subscribers) { | |
internal.current.subscribers = internal.current.subscribers.filter( | |
(s) => s !== ref | |
); | |
} | |
}; | |
} | |
useAnimationFrame(renderFrame); | |
return ( | |
<CanvasReactContext.Provider value={{ subscribe }}> | |
<canvas {...props} ref={canvasRef} /> | |
{props.children} | |
</CanvasReactContext.Provider> | |
); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment