UNPKG

spawn-confetti

Version:

🎉 Lightweight JavaScript confetti effect that spawns at the mouse position on demand.

166 lines (142 loc) • 7.39 kB
class Vector { constructor(x, y) { this.x = x this.y = y } set(x, y) { if (x instanceof Vector) { this.x = x.x this.y = x.y } else { this.x = x this.y = y } return this } magnitude() { return Math.sqrt(this.x ** 2 + this.y ** 2) } } class Confetti { // Configurable settings static acceleration = new Vector(0, .25) static maxVel = new Vector(1.5, 10) static drag = new Vector(.98, 1) static colors = ["#f44a4a", "#fb8f23", "#fee440", "#7aff60", "#00f5d4", "#00bbf9", "#9b5de5", "#f15bb5"] static shapes = [ `<rect x="5" y="0" width="6" height="16"/>`, // Rectangle `<path width="16" height="16" d="M0,12 Q4,4 8,12 Q12,20 16,12" stroke-width="5" fill="none"/>`, // Squigly line `<circle cx="9" cy="9" r="5.5"/>`, // Circle `<polygon points="9,2.072 17,15.928 1,15.928"/>` // Triangle ] static fadeOut = true constructor(lifetime) { this.batchId = batchId // Batch ID to allow removal of some confetti without removing all of them this.color = Confetti.colors[Math.floor(Math.random() * Confetti.colors.length)] // Random color this.pos = new Vector(0, 0) this.rotation = new Vector(0, Math.random() * 360) this.lifetime = lifetime // Lifetime to delete confetti after a while this.vel = new Vector(0, 0) this.angVel = new Vector(0, 0) // Rotational velocity this.displayElement() // Creating and displaying the confetti particle } update() { if (this.vel.y <= Confetti.maxVel.y) this.vel.y += Confetti.acceleration.y // If velocity less than .01 make it 0, else apply Confetti.drag this.vel.magnitude() > .01 ? this.vel.x *= Confetti.drag.x : this.vel.x = 0 // Update particle position this.pos.x += this.vel.x this.pos.y += this.vel.y // Display particle position this.element.style.left = `${this.pos.x}` this.element.style.top = `${this.pos.y}` // Rotating the particle this.rotation.x += this.angVel.x this.rotation.y += this.angVel.y this.element.style.transform = `rotateX(${this.rotation.x % 360}deg) rotate(${this.rotation.y % 360}deg)` // % 360 keeps it between 0 and 360 const now = performance.now(); // Get the current timestamp if (!this.startTime) this.startTime = now; // Initialize the start time if not already set const elapsedTime = now - this.startTime; if (elapsedTime > this.lifetime) this.element.remove(); // Delete particle after lifetime // Fade out when particle reaches 3 / 4 of its lifetime (if enabled in configs) if (Confetti.fadeOut) this.element.style.opacity = elapsedTime > this.lifetime * 3 / 4 // Has particle passed more than 4/5 of its lifetime? ? (1 - (elapsedTime - this.lifetime * 3 / 4) / (this.lifetime / 4)) // Start fading out : 1 // Don't change opacity } displayElement() { // Creating confetti particle this.element = document.createElementNS("http://www.w3.org/2000/svg", "svg") // Setting confetti particle's attributes this.element.setAttribute("xmlns", "http://www.w3.org/2000/svg") this.element.setAttribute("width", `14px`) this.element.setAttribute("height", `14px`) this.element.setAttribute("viewBox", "0 0 18 18") this.element.style.position = "absolute" this.element.classList.add("confetti-particle") // Giving the confetti particle a random shape this.element.innerHTML = Confetti.shapes[Math.floor(Math.random() * Confetti.shapes.length)] // Pick a random shape // Giving the confetti particle a random color if (this.element.firstChild.getAttribute('fill') === "none") this.element.firstChild.setAttribute('stroke', Confetti.colors[Math.floor(Math.random() * Confetti.colors.length)]) // Change the stroke only if fill="none", this is for path shapes (adding stroke to non-path shapes ruins them) this.element.firstChild.setAttribute('fill', Confetti.colors[Math.floor(Math.random() * Confetti.colors.length)]) confettiContainer.appendChild(this.element) // Add the confetti particle to the container } } export {Confetti}; window.Confetti = Confetti // Getting the mouse position and storing it to spawn the confetti at its location let mouseX = 0, mouseY = 0 document.addEventListener("mousemove", (e) => { mouseX = e.clientX mouseY = e.clientY }) const confettiContainer = document.createElement("div") // Creating a container for the confetti const confettiParticles = [] // Creating an array for all the confetti let batchId = 0 // Initializing batch ID function spawnConfetti({ // Default values amount = 30, x = "mouse", y = "mouse", velXRange = [-5, 5], velYRange = [-8, 0], angVelXRange = [0, 0], angVelZRange = [6, 12], lifetime = 2000 } = {}) { // Setting the confetti container attributes confettiContainer.style.position = "fixed" confettiContainer.style.top = "0" confettiContainer.style.width = "100%" confettiContainer.style.height = "100%" confettiContainer.style.zIndex = "99999" confettiContainer.style.pointerEvents = "none" confettiContainer.style.overflow = "hidden" confettiContainer.classList.add("confetti-container") document.body.appendChild(confettiContainer) batchId++ // Incrementing the batch ID for next batch of confetti for (let i = 0; i < amount; i++) { // Creating confetti const particle = new Confetti(lifetime) confettiParticles.push(particle) // Add to array } // Check "mouse" pos keyword for x and y params if (x === "mouse") x = mouseX if (y === "mouse") y = mouseY // Check "center" pos keyword for x and y params if (x === "center") x = window.innerWidth / 2 if (y === "center") y = window.innerHeight / 2 // Check "max" pos keyword for x and y params if (x === "max") x = window.innerWidth if (y === "max") y = window.innerHeight confettiParticles.forEach((particle) => { // Setting confetti stuff // Setting the range of velocities let velX = Math.random() * (velXRange[1] - velXRange[0]) + velXRange[0] let velY = Math.random() * (velYRange[1] - velYRange[0]) + velYRange[0] let angVelX = Math.random() * (angVelXRange[1] - angVelXRange[0]) + angVelXRange[0] let angVelZ = Math.random() * (angVelZRange[1] - angVelZRange[0]) + angVelZRange[0] if (particle.batchId === batchId) { particle.pos.set(x, y) particle.vel.set(velX, velY) particle.angVel.set(angVelX, angVelZ) } }) } export {spawnConfetti}; window.spawnConfetti = spawnConfetti // Exporting the function // Animate one frame function animate() { confettiParticles.forEach((particle) => { particle.update() }) // Update each particle position and rotation requestAnimationFrame(animate) } animate()