stacker.news/components/fireworks.js

273 lines
5.4 KiB
JavaScript

import { useCallback, createContext, useContext, useState, useEffect } from 'react'
import Particles from 'react-particles'
import { loadFireworksPreset } from 'tsparticles-preset-fireworks'
import styles from './fireworks.module.css'
import {
rgbToHsl,
setRangeValue,
stringToRgb
} from 'tsparticles-engine'
import useDarkMode from 'use-dark-mode'
export const FireworksContext = createContext({
strike: () => {}
})
export const FireworksConsumer = FireworksContext.Consumer
export function useFireworks () {
const { strike } = useContext(FireworksContext)
return strike
}
export function FireworksProvider ({ children }) {
const [cont, setCont] = useState()
const [context, setContext] = useState({ strike: () => {} })
const darkMode = useDarkMode(false)
useEffect(() => {
setContext({
strike: () => {
cont?.addEmitter(
{
direction: 'top',
life: {
count: 1,
duration: 0.1,
delay: 0.1
},
rate: {
delay: 0,
quantity: 1
},
size: {
width: 10,
height: 0
},
position: {
y: 100,
x: 50
}
})
}
})
}, [cont])
const particlesLoaded = useCallback((container) => {
setCont(container)
}, [])
const particlesInit = useCallback(async engine => {
// you can initiate the tsParticles instance (engine) here, adding custom shapes or presets
// this loads the tsparticles package bundle, it's the easiest method for getting everything ready
// starting from v2 you can add only the features you need reducing the bundle size
await loadFireworksPreset(engine)
}, [])
return (
<FireworksContext.Provider value={context}>
<Particles
className={styles.fireworks}
init={particlesInit}
loaded={particlesLoaded}
options={darkMode.value ? darkOptions : lightOptions}
/>
{children}
</FireworksContext.Provider>
)
}
const fixRange = (value, min, max) => {
const diffSMax = value.max > max ? value.max - max : 0
let res = setRangeValue(value)
if (diffSMax) {
res = setRangeValue(value.min - diffSMax, max)
}
const diffSMin = value.min < min ? value.min : 0
if (diffSMin) {
res = setRangeValue(0, value.max + diffSMin)
}
return res
}
const fireworksOptions = ['#ff595e', '#ffca3a', '#8ac926', '#1982c4', '#6a4c93']
.map((color) => {
const rgb = stringToRgb(color)
if (!rgb) {
return undefined
}
const hsl = rgbToHsl(rgb)
const sRange = fixRange({ min: hsl.s - 30, max: hsl.s + 30 }, 0, 100)
const lRange = fixRange({ min: hsl.l - 30, max: hsl.l + 30 }, 0, 100)
return {
color: {
value: {
h: hsl.h,
s: sRange,
l: lRange
}
},
stroke: {
width: 0
},
number: {
value: 0
},
opacity: {
value: {
min: 0.1,
max: 1
},
animation: {
enable: true,
speed: 0.7,
sync: false,
startValue: 'max',
destroy: 'min'
}
},
shape: {
type: 'circle'
},
size: {
value: { min: 1, max: 2 },
animation: {
enable: true,
speed: 5,
count: 1,
sync: false,
startValue: 'min',
destroy: 'none'
}
},
life: {
count: 1,
duration: {
value: {
min: 1,
max: 2
}
}
},
move: {
decay: { min: 0.075, max: 0.1 },
enable: true,
gravity: {
enable: true,
inverse: false,
acceleration: 5
},
speed: { min: 5, max: 15 },
direction: 'none',
outMode: {
top: 'destroy',
default: 'bounce'
}
}
}
})
.filter((t) => t !== undefined)
const particlesOptions = (theme) => ({
number: {
value: 0
},
destroy: {
mode: 'split',
bounds: {
top: { min: 5, max: 40 }
},
split: {
sizeOffset: false,
count: 1,
factor: {
value: 0.333333
},
rate: {
value: { min: 75, max: 150 }
},
particles: fireworksOptions
}
},
life: {
count: 1
},
shape: {
type: 'line'
},
size: {
value: {
min: 0.1,
max: 50
},
animation: {
enable: true,
sync: true,
speed: 90,
startValue: 'max',
destroy: 'min'
}
},
rotate: {
path: true
},
stroke: {
color: {
value: theme === 'dark' ? '#fff' : '#aaa'
},
width: 1
},
move: {
enable: true,
gravity: {
acceleration: 15,
enable: true,
inverse: true,
maxSpeed: 100
},
speed: {
min: 10,
max: 20
},
outModes: {
default: 'destroy',
top: 'none'
},
trail: {
fillColor: theme === 'dark' ? '#000' : '#f5f5f7',
enable: true,
length: 10
}
}
})
const darkOptions = {
fullScreen: { enable: true },
detectRetina: true,
background: {
color: '#000'
},
zIndex: -1,
fpsLimit: 120,
emitters: [],
particles: particlesOptions('dark')
}
const lightOptions = {
fullScreen: { enable: true },
detectRetina: true,
background: {
color: '#fff'
},
zIndex: -1,
fpsLimit: 120,
emitters: [],
particles: particlesOptions('light')
}