import { useEffect, useRef } from 'react'; const createCanvasConfetti = () => { const confetti = document.createElement('canvas'); confetti.id = 'confetti'; document.body.appendChild(confetti); confetti.style.position = 'fixed'; confetti.style.left = '0'; confetti.style.top = '0'; confetti.style.pointerEvents = 'none'; return confetti; }; const useConfetti = (ref: React.RefObject) => { const requestRef = useRef(); useEffect(() => { let confetti = document.getElementById('confetti') as HTMLCanvasElement; if (!ref) return; const x = ref?.current?.getBoundingClientRect().left; const y = ref?.current?.getBoundingClientRect().top; if (!confetti) { confetti = createCanvasConfetti(); } if (!confetti) return; confetti.width = document.body.clientWidth; confetti.height = document.body.clientHeight; confetti.style.zIndex = '99999'; // global variables const confettiCtx = confetti.getContext('2d'); if (!confettiCtx) return; const confettiElements: any[] = []; const container = { w: confetti.clientWidth, h: confetti.clientHeight, x: undefined, y: undefined, }; // helper const rand = (min: number, max: number) => Math.random() * (max - min) + min; const confettiParams = { // number of confetti per "explosion" number: 200, // min and max size for each rectangle size: { x: [5, 20], y: [10, 18] }, // power of explosion initSpeed: 15, // defines how fast particles go down after blast-off gravity: 0.15, // how wide is explosion drag: 0.04, // how slow particles are falling terminalVelocity: 6, // how fast particles are rotating around themselves flipSpeed: 0.017, }; const colors = [ { front: '#14CC9E', back: '#0E8C6D' }, { front: '#F2C53D', back: '#73500B' }, { front: '#E065C2', back: '#591B3F' }, { front: '#A63275', back: '#591B3F' }, { front: '#664E8B', back: '#3d2f53' }, { front: '#4CA6FF', back: '#200336622f48' }, { front: '#0E8C6D', back: '#045944' }, ]; function Conf(this: any) { this.randomModifier = rand(-1, 1); this.colorPair = colors[Math.floor(rand(0, colors.length))]; this.dimensions = { x: rand(confettiParams.size.x[0], confettiParams.size.x[1]), y: rand(confettiParams.size.y[0], confettiParams.size.y[1]), }; this.position = { x, y, }; this.rotation = rand(0, 2 * Math.PI); this.scale = { x: 1, y: 1 }; this.velocity = { x: rand(-confettiParams.initSpeed, confettiParams.initSpeed) * 1, y: rand(-confettiParams.initSpeed, confettiParams.initSpeed), }; this.flipSpeed = rand(0.2, 1.5) * confettiParams.flipSpeed; if (this.position.y <= container.h) { this.velocity.y = -Math.abs(this.velocity.y); } this.terminalVelocity = rand(1, 1.5) * confettiParams.terminalVelocity; this.update = function () { this.velocity.x *= 0.98; this.position.x += this.velocity.x; this.velocity.y += this.randomModifier * confettiParams.drag; this.velocity.y += confettiParams.gravity; this.velocity.y = Math.min(this.velocity.y, this.terminalVelocity); this.position.y += this.velocity.y; this.scale.y = Math.cos((this.position.y + this.randomModifier) * this.flipSpeed); this.color = this.scale.y > 0 ? this.colorPair.front : this.colorPair.back; }; } function updateConfetti() { if (!confettiCtx) return; confettiCtx.clearRect(0, 0, container.w, container.h); confettiElements.forEach((c) => { c.update(); confettiCtx.translate(c.position.x, c.position.y); confettiCtx.rotate(c.rotation); const width = c.dimensions.x * c.scale.x; const height = c.dimensions.y * c.scale.y; confettiCtx.fillStyle = c.color; confettiCtx.fillRect(-0.5 * width, -0.5 * height, width, height); confettiCtx.setTransform(1, 0, 0, 1, 0, 0); }); confettiElements.forEach((c, idx) => { if ( c.position.y > container.h || c.position.x < -0.5 * (container as any).x || c.position.x > 1.5 * (container as any).x ) { confettiElements.splice(idx, 1); } }); requestRef.current = window.requestAnimationFrame(updateConfetti); } function addConfetti() { for (let i = 0; i < confettiParams.number; i++) { confettiElements.push(new (Conf as any)()); } } setTimeout(() => { addConfetti(); updateConfetti(); }, 400); return () => { cancelAnimationFrame(requestRef.current); try { confetti && document.body.removeChild(confetti); } catch (e) { console.log(e); } }; }, [ref]); }; export default useConfetti;