Skip to content

Instantly share code, notes, and snippets.

@dayhaysoos
Last active August 7, 2020 00:10
Show Gist options
  • Save dayhaysoos/53246a1051c77fa5647b9badb92c3c7b to your computer and use it in GitHub Desktop.
Save dayhaysoos/53246a1051c77fa5647b9badb92c3c7b to your computer and use it in GitHub Desktop.
import React from 'react'
import { Box } from 'theme-ui'
import { keyframes } from '@emotion/core'
// Default color is a bright yellow
const DEFAULT_COLOR = 'hsl(50deg, 100%, 50%)'
const range = (start, end, step = 1) => {
let output = []
if (typeof end === 'undefined') {
end = start
start = 0
}
for (let i = start; i < end; i += step) {
output.push(i)
}
return output
}
const random = (min, max) => Math.floor(Math.random() * (max - min)) + min
const colors = [
'#6032C7',
'#05b993',
DEFAULT_COLOR,
'#E50A68',
]
const useRandomInterval = (callback, minDelay, maxDelay) => {
const timeoutId = React.useRef(null)
const savedCallback = React.useRef(callback)
React.useEffect(() => {
savedCallback.current = callback
})
React.useEffect(() => {
let isEnabled = typeof minDelay === 'number' && typeof maxDelay === 'number'
if (isEnabled) {
const handleTick = () => {
const nextTickAt = random(minDelay, maxDelay)
timeoutId.current = window.setTimeout(() => {
savedCallback.current()
handleTick()
}, nextTickAt)
}
handleTick()
}
return () => window.clearTimeout(timeoutId.current)
}, [minDelay, maxDelay])
const cancel = React.useCallback(function () {
window.clearTimeout(timeoutId.current)
}, [])
return cancel
}
const growAndShrink = keyframes`
0% {
transform: scale(0);
}
50% {
transform: scale(1);
}
100% {
transform: scale(0);
}
`
const spin = keyframes`
from {
transform: rotate(0deg);
}
to {
transform: rotate(180deg);
}
`
const comeInOut = keyframes`
0% {
transform: scale(0);
}
50% {
transform: scale(1);
}
100% {
transform: scale(0);
}
`
const generateSparkle = (color) => {
const sparkle = {
id: String(random(10000, 99999)),
createdAt: Date.now(),
color: colors[random(0, 4)],
size: random(10, 30),
style: {
top: random(0, 80) + '%',
left: random(0, 80) + '%',
},
}
return sparkle
}
function Sparkle({ color, size, style }) {
const path =
'M242.593 50.6596C249.994 32.2864 276.006 32.2864 283.407 50.6596L333.818 175.806C336.095 181.459 340.615 185.914 346.3 188.109L472.852 236.977C491.618 244.224 491.618 270.776 472.852 278.023L346.3 326.891C340.615 329.086 336.095 333.541 333.818 339.194L283.407 464.34C276.006 482.714 249.994 482.714 242.593 464.34L192.182 339.194C189.905 333.541 185.385 329.086 179.7 326.891L53.1483 278.023C34.3815 270.776 34.3816 244.224 53.1484 236.977L179.7 188.109C185.385 185.914 189.905 181.459 192.182 175.806L242.593 50.6596Z'
return (
<Box
sx={{ display: 'block', position: 'absolute', animation: `${growAndShrink} 600ms ease-in-out forwards` }}
style={style}
as="span"
>
<svg
width={size}
height={size}
viewBox="0 0 528 528"
fill="none"
style={style}
sx={{
display: 'block',
animation: `${spin} 600ms ease-in-out forwards`,
'@media(prefers-reduced-motion: no-preference)': {
animation: `${spin} 1000ms linear`,
},
}}
>
<path fill={color} d={path} />
</svg>
</Box>
)
}
function Sparkles({ color = DEFAULT_COLOR, children }) {
const [sparkles, setSparkles] = React.useState(() => {
return range(3).map(() => generateSparkle(color))
})
useRandomInterval(
() => {
const sparkle = generateSparkle(color)
const now = Date.now()
const nextSparkles = sparkles.filter((sp) => {
const delta = now - sp.createdAt
return delta < 750
})
nextSparkles.push(sparkle)
setSparkles(nextSparkles)
},
500,
500
)
return (
<Box
as="span"
sx={{
position: 'relative',
display: 'inline-block',
}}
>
{sparkles.map((sparkle) => (
<Sparkle
key={sparkle.id}
color={sparkle.color}
size={sparkle.size}
style={sparkle.style}
/>
))}
<Box sx={{ position: 'relative', zIndex: [-1, 1][random(0,1)], fontWeight: 'bold' }}>
{children}
</Box>
</Box>
)
}
export default Sparkles
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment