UNPKG

react-snowfall

Version:

A react component that creates a snowfall effect

118 lines (94 loc) 3.43 kB
import Snowflake, { SnowflakeConfig, defaultConfig } from './Snowflake' import { targetFrameTime } from './config' export interface SnowfallCanvasConfig extends SnowflakeConfig { /** * The number of snowflakes to be rendered. * * The default value is 150. */ snowflakeCount: number } export class SnowfallCanvas { private lastUpdate = Date.now() private snowflakes: Snowflake[] = [] private config: SnowfallCanvasConfig #ctx: CanvasRenderingContext2D | null get ctx() { return this.#ctx } #canvas: HTMLCanvasElement get canvas() { return this.#canvas } set canvas(canvas: HTMLCanvasElement) { this.#canvas = canvas this.#ctx = canvas.getContext('2d') } constructor(canvas: HTMLCanvasElement, config: Partial<SnowfallCanvasConfig>) { this.#canvas = canvas this.#ctx = canvas.getContext('2d') this.config = { snowflakeCount: 150, ...defaultConfig, ...config } this.snowflakes = [] this.snowflakes = Snowflake.createSnowflakes(canvas, config.snowflakeCount || 150, config) this.play() } /** * Updates the config used for the snowfall animation, if the number of snowflakes * has changed then this will create new or remove existing snowflakes gracefully * to retain the position of as many existing snowflakes as possible. */ updateConfig(config: Partial<SnowfallCanvasConfig>) { this.config = { ...this.config, ...config } const sizeDifference = this.config.snowflakeCount - this.snowflakes.length if (sizeDifference > 0) { this.snowflakes = [...this.snowflakes, ...Snowflake.createSnowflakes(this.canvas, sizeDifference, config)] } if (sizeDifference < 0) { this.snowflakes = this.snowflakes.slice(0, this.config.snowflakeCount) } this.snowflakes.forEach((snowflake) => snowflake.updateConfig(this.config)) } /** * Updates the location of each snowflake based on the number of frames passed then * clears the canvas and draws each snowflake. */ private render(framesPassed = 1) { const { ctx, canvas, snowflakes } = this const { offsetWidth, offsetHeight } = canvas // Update the position of each snowflake snowflakes.forEach((snowflake) => snowflake.update(offsetWidth, offsetHeight, framesPassed)) // Render them if the canvas is available if (ctx) { ctx.setTransform(1, 0, 0, 1, 0, 0) ctx.clearRect(0, 0, offsetWidth, offsetHeight) snowflakes.forEach((snowflake) => snowflake.draw(ctx)) } } private animationFrame: number | undefined /** * The animation loop, will calculate the time since the last render and update * the position of the snowflakes appropriately before queueing another frame. */ private loop() { // Update based on time passed so that a slow frame rate won't slow down the snowflake const now = Date.now() const msPassed = Date.now() - this.lastUpdate this.lastUpdate = now // Frames that would have passed if running at 60 fps const framesPassed = msPassed / targetFrameTime this.render(framesPassed) this.animationFrame = requestAnimationFrame(() => this.loop()) } /** Start the animation playing. */ play() { this.loop() } /** Pause the animation. */ pause() { if (this.animationFrame) { cancelAnimationFrame(this.animationFrame) this.animationFrame = undefined } } } export default SnowfallCanvas