Skip to content

Instantly share code, notes, and snippets.

@foucdeg
Created August 28, 2023 09:00
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save foucdeg/a6d7b9b0663ede204b0bdff5efc1eae3 to your computer and use it in GitHub Desktop.
Save foucdeg/a6d7b9b0663ede204b0bdff5efc1eae3 to your computer and use it in GitHub Desktop.
React + Typescript Confetti
import React, {useRef} from "react"
import {createPortal} from "react-dom"
import {useFela} from "react-fela"
import {initConfetti, Confetto, loop, resizeCanvas} from "./lib"
export default function Confetti() {
const particlesRef = useRef<Confetto[]>([])
// BYO styling lib
const {css} = useFela()
function onCanvas(canvas: HTMLCanvasElement | null) {
if (!canvas) return
resizeCanvas(canvas)
const context = canvas.getContext("2d")
if (!context) return
setTimeout(() => {
initConfetti(particlesRef.current, canvas)
loop(particlesRef.current, canvas, context)
}, 300)
}
return createPortal(
<canvas
ref={onCanvas}
// BYO styling lib
className={css({
position: "fixed",
top: "0",
left: "0",
width: "100vw",
height: "100vh",
background: "none",
zIndex: 1000,
pointerEvents: "none",
})}
/>,
document.body,
)
}
// Source: https://www.codehim.com/animation-effects/javascript-confetti-explosion-effect/
const confettiCount = 300
const gravity = 0.5
const terminalVelocity = 5
const drag = 0.075
const colors = [
{front: "red", back: "darkred"},
{front: "green", back: "darkgreen"},
{front: "blue", back: "darkblue"},
{front: "yellow", back: "darkyellow"},
{front: "orange", back: "darkorange"},
{front: "pink", back: "darkpink"},
{front: "purple", back: "darkpurple"},
{front: "turquoise", back: "darkturquoise"},
]
export type Confetto = {
color: {front: string; back: string}
dimensions: {x: number; y: number}
position: {x: number; y: number}
rotation: number
scale: {x: number; y: number}
velocity: {x: number; y: number}
}
export function resizeCanvas(canvas: HTMLCanvasElement) {
canvas.width = window.innerWidth
canvas.height = window.innerHeight
}
function randomRange(min: number, max: number) {
return Math.random() * (max - min) + min
}
export function initConfetti(confetti: Confetto[], canvas: HTMLCanvasElement) {
for (let i = 0; i < confettiCount; i++) {
confetti.push({
color: colors[Math.floor(randomRange(0, colors.length))],
dimensions: {
x: randomRange(10, 20),
y: randomRange(10, 30),
},
position: {
x: randomRange(0, canvas.width),
y: canvas.height - 1,
},
rotation: randomRange(0, 2 * Math.PI),
scale: {
x: 1,
y: 1,
},
velocity: {
x: randomRange(-25, 25),
y: randomRange(0, -50),
},
})
}
}
export function loop(confetti: Confetto[], canvas: HTMLCanvasElement, ctx: CanvasRenderingContext2D) {
ctx.clearRect(0, 0, canvas.width, canvas.height)
confetti.forEach((confetto, index) => {
const width = confetto.dimensions.x * confetto.scale.x
const height = confetto.dimensions.y * confetto.scale.y
// Move canvas to position and rotate
ctx.translate(confetto.position.x, confetto.position.y)
ctx.rotate(confetto.rotation)
// Apply forces to velocity
confetto.velocity.x -= confetto.velocity.x * drag
confetto.velocity.y = Math.min(confetto.velocity.y + gravity, terminalVelocity)
confetto.velocity.x += Math.random() > 0.5 ? Math.random() : -Math.random()
// Set position
confetto.position.x += confetto.velocity.x
confetto.position.y += confetto.velocity.y
// Delete confetti when out of frame
if (confetto.position.y >= canvas.height) confetti.splice(index, 1)
// Loop confetto x position
if (confetto.position.x > canvas.width) confetto.position.x = 0
if (confetto.position.x < 0) confetto.position.x = canvas.width
// Spin confetto by scaling y
confetto.scale.y = Math.cos(confetto.position.y * 0.1)
ctx.fillStyle = confetto.scale.y > 0 ? confetto.color.front : confetto.color.back
// Draw confetti
ctx.fillRect(-width / 2, -height / 2, width, height)
// Reset transform matrix
ctx.setTransform(1, 0, 0, 1, 0, 0)
})
window.requestAnimationFrame(() => loop(confetti, canvas, ctx))
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment