Last active
February 12, 2020 08:51
-
-
Save dcollien/b88d2fdd6d08b696a1e1b6e050de9ac0 to your computer and use it in GitHub Desktop.
Animated Canvas Component
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, { useRef, useEffect, useCallback, useState } from "react"; | |
type UpdateHandler = (dt: number) => void; | |
type ContextRenderer = (ctx: CanvasRenderingContext2D) => void; | |
export interface IAnimatedCanvasProps { | |
width: number; | |
height: number; | |
onFrame: UpdateHandler; | |
render: ContextRenderer; | |
isAnimating?: boolean; | |
} | |
class Runtime { | |
update: UpdateHandler; | |
redraw: () => void; | |
isAnimating: boolean; | |
frameRequest?: number; | |
lastStep?: number; | |
constructor(update: UpdateHandler, redraw: () => void) { | |
this.update = update; | |
this.redraw = redraw; | |
this.isAnimating = false; | |
} | |
run() { | |
if (this.isAnimating) { | |
return; | |
} | |
this.isAnimating = true; | |
const step = () => { | |
this.step(); | |
this.frameRequest = window.requestAnimationFrame(step); | |
}; | |
step(); | |
} | |
stop() { | |
if (this.frameRequest !== undefined) { | |
window.cancelAnimationFrame(this.frameRequest); | |
} | |
this.frameRequest = undefined; | |
this.isAnimating = false; | |
} | |
step() { | |
const now = Date.now(); | |
const dt = (now - this.lastStep!) / 1000; | |
this.lastStep = now; | |
this.update(dt); | |
this.redraw(); | |
} | |
} | |
export const AnimatedCanvas: React.FC<IAnimatedCanvasProps> = ({ | |
width, | |
height, | |
onFrame, | |
render, | |
isAnimating = true | |
}) => { | |
const canvasRef = useRef<HTMLCanvasElement>(null); | |
const [runtime, setRuntime] = useState<Runtime>(); | |
// when the canvas changes, make a new redraw fn for the new context | |
const redraw = useCallback(() => { | |
const ctx = canvasRef.current?.getContext("2d") | |
if (ctx) { | |
render(ctx); | |
} | |
}, [canvasRef.current, render]); | |
// when the onFrame handler or redraw fn changes, make a new runtime | |
useEffect(() => { | |
runtime?.stop(); | |
setRuntime(new Runtime(onFrame, redraw)); | |
}, [onFrame, redraw]); | |
// when the runtime, or isAnimating flag changes, start/stop the runtime | |
useEffect(() => { | |
if (isAnimating) { | |
runtime?.run(); | |
} else { | |
runtime?.stop(); | |
} | |
}, [runtime, isAnimating]); | |
return <canvas width={width} height={height} ref={canvasRef}></canvas>; | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment