Skip to content

Commit

Permalink
feat(SnowflakeConfig): allow additional snowflake properties to be ov…
Browse files Browse the repository at this point in the history
…erridden via the Snowfall props
  • Loading branch information
cahilfoley committed Feb 23, 2021
1 parent c47c36f commit 5d7b3f5
Show file tree
Hide file tree
Showing 4 changed files with 116 additions and 14 deletions.
28 changes: 22 additions & 6 deletions src/Snowfall.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,38 @@
import React, { useCallback, useEffect, useMemo, useRef } from 'react'
import React, { useCallback, useEffect, useRef } from 'react'
import { targetFrameTime } from './config'
import { useComponentSize, useSnowfallStyle, useSnowflakes } from './hooks'
import { useComponentSize, useSnowfallStyle, useSnowflakes, useDeepMemo } from './hooks'
import { SnowflakeProps, defaultConfig } from './Snowflake'

export interface SnowfallProps {
color?: string
export interface SnowfallProps extends Partial<SnowflakeProps> {
/**
* The number of snowflakes to be rendered.
*
* The default value is 150.
*/
snowflakeCount?: number
/**
* Any style properties that will be passed to the canvas element.
*/
style?: React.CSSProperties
}

const Snowfall = ({ color = '#dee4fd', snowflakeCount = 150, style }: SnowfallProps = {}) => {
const Snowfall = ({
color = defaultConfig.color,
changeFrequency = defaultConfig.changeFrequency,
radius = defaultConfig.radius,
speed = defaultConfig.speed,
wind = defaultConfig.wind,
snowflakeCount = 150,
style,
}: SnowfallProps = {}) => {
const mergedStyle = useSnowfallStyle(style)

const canvasRef = useRef<HTMLCanvasElement>()
const canvasSize = useComponentSize(canvasRef)
const animationFrame = useRef(0)

const lastUpdate = useRef(Date.now())
const config = useMemo(() => ({ color }), [color])
const config = useDeepMemo<SnowflakeProps>({ color, changeFrequency, radius, speed, wind })
const snowflakes = useSnowflakes(canvasRef, snowflakeCount, config)

const updateCanvasRef = (element: HTMLCanvasElement) => {
Expand Down
45 changes: 39 additions & 6 deletions src/Snowflake.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,53 @@
import { lerp, random } from './utils'

export interface SnowflakeProps {
/** The color of the snowflake, can be any valid CSS color. */
color: string
radius: [number, number]
speed: [number, number]
wind: [number, number]
/**
* The minimum and maximum radius of the snowflake, will be
* randomly selected within this range.
*
* The default value is `[0.5, 3.0]`.
*/
radius: [minimumRadius: number, maximumRadius: number]
/**
* The minimum and maximum speed of the snowflake.
*
* The speed determines how quickly the snowflake moves
* along the y axis (vertical speed).
*
* The values will be randomly selected within this range.
*
* The default value is `[1.0, 3.0]`.
*/
speed: [minimumSpeed: number, maximumSpeed: number]
/**
* The minimum and maximum wind of the snowflake.
*
* The wind determines how quickly the snowflake moves
* along the x axis (horizontal speed).
*
* The values will be randomly selected within this range.
*
* The default value is `[-0.5, 2.0]`.
*/
wind: [minimumWind: number, maximumWind: number]
/**
* The frequency in frames that the wind and speed values
* will update.
*
* The default value is 200.
*/
changeFrequency: number
}

export type SnowflakeConfig = Partial<SnowflakeProps>

const defaultConfig: SnowflakeProps = {
export const defaultConfig: SnowflakeProps = {
color: '#dee4fd',
radius: [0.5, 3.0],
speed: [1, 3],
wind: [-0.5, 2],
speed: [1.0, 3.0],
wind: [-0.5, 2.0],
changeFrequency: 200,
}

Expand Down
51 changes: 50 additions & 1 deletion src/hooks.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,16 @@
import { useCallback, useLayoutEffect, useEffect, useState, MutableRefObject, CSSProperties, useMemo } from 'react'
import {
DependencyList,
EffectCallback,
useCallback,
useLayoutEffect,
useEffect,
useRef,
useState,
MutableRefObject,
CSSProperties,
useMemo,
} from 'react'
import isEqual from 'react-fast-compare'
import Snowflake, { SnowflakeConfig } from './Snowflake'
import { snowfallBaseStyle } from './config'
import { getSize } from './utils'
Expand Down Expand Up @@ -116,3 +128,40 @@ export const useSnowfallStyle = (overrides?: CSSProperties) => {

return styles
}

/**
* Same as `React.useEffect` but uses a deep comparison on the dependency array. This should only
* be used when working with non-primitive dependencies.
*
* @param effect Effect callback to run
* @param deps Effect dependencies
*/
export function useDeepCompareEffect(effect: EffectCallback, deps: DependencyList) {
const ref = useRef<DependencyList>(deps)

// Only update the current dependencies if they are not deep equal
if (!isEqual(deps, ref.current)) {
ref.current = deps
}

useEffect(effect, ref.current)
}

/**
* Utility hook to stabilize a reference to a value, the returned value will always match the input value
* but (unlike an inline object) will maintain [SameValueZero](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero)
* equality until a change is made.
*
* @example
*
* const obj = useDeepMemo({ foo: 'bar', bar: 'baz' }) // <- inline object creation
* const prevValue = usePrevious(obj) // <- value from the previous render
* console.log(obj === prevValue) // <- always logs true until value changes
*/
export function useDeepMemo<T>(value: T): T {
const [state, setState] = useState(value)

useDeepCompareEffect(() => setState(value), [value])

return state
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ interface Window {
ResizeObserver: ResizeObserver
}

interface ResizeObserverOptions {
box?: 'border-box' | 'content-box' | 'device-pixel-content-box'
}

/**
* The ResizeObserver interface is used to observe changes to Element's content
* rect.
Expand All @@ -16,7 +20,7 @@ interface ResizeObserver {
/**
* Adds target to the list of observed elements.
*/
observe: (target: Element) => void
observe: (target: Element, options?: ResizeObserverOptions) => void

/**
* Removes target from the list of observed elements.
Expand Down

0 comments on commit 5d7b3f5

Please sign in to comment.