Created
March 28, 2025 15:40
-
-
Save luizbills/361e21560cf91bc135d5976edef2a73c to your computer and use it in GitHub Desktop.
Confetti emitter for Litecanvas
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// based on https://play.kaplayjs.com/?example=confetti | |
litecanvas({}) | |
function init() { | |
loadScript('https://cdn.jsdelivr.net/npm/[email protected]/build/stats.min.js', () => { | |
stats = new Stats() | |
document.body.appendChild(stats.dom) | |
listen('before:update', () => stats.begin()) | |
listen('after:draw', () => stats.end()) | |
}) | |
} | |
function tapped(tapx, tapy) { | |
addConfetti({ | |
pos: vec(tapx, tapy), | |
heading: 45 | |
}) | |
} | |
function draw() { | |
cls(0) | |
} | |
// default confetti settings | |
const DEF_COUNT = 100; | |
const DEF_GRAVITY = 800; | |
const DEF_AIR_DRAG = 0.9; | |
const DEF_VELOCITY = [1000, 4000]; | |
const DEF_ANGULAR_VELOCITY = [-200, 200]; | |
const DEF_FADE = 0.3; | |
const DEF_SPREAD = 60; | |
const DEF_SPIN = [2, 8]; | |
function addConfetti(opt = {}) { | |
const vecRand = (min, max) => { | |
return vec( | |
rand(min, max), | |
rand(min, max) | |
) | |
} | |
for (let i = 0; i < (opt.count || DEF_COUNT); i++) { | |
const p = { | |
pos: vec(opt.pos || ZERO), | |
shape: choose([[rand(5,20),rand(5,20)],[rand(3,10)]]), | |
color: randi(4,11), | |
opacity: 1, | |
lifespan: 4, | |
angle: rand(0,360), | |
scale: vec(1,1) | |
} | |
const spin = rand(DEF_SPIN[0], DEF_SPIN[1]); | |
const gravity = opt.gravity || DEF_GRAVITY; | |
const airDrag = opt.airDrag || DEF_AIR_DRAG; | |
const heading = (opt.heading || 0) - 90; | |
const spread = opt.spread || DEF_SPREAD; | |
const head = heading + rand(-spread / 2, spread / 2); | |
const fade = opt.fade || DEF_FADE; | |
const vel = opt.velocity || rand(DEF_VELOCITY[0], DEF_VELOCITY[1]); | |
let velX = Math.cos(deg2rad(head)) * vel; | |
let velY = Math.sin(deg2rad(head)) * vel; | |
const velA = opt.angularVelocity | |
|| rand(DEF_ANGULAR_VELOCITY[0], DEF_ANGULAR_VELOCITY[1]) | |
const endDraw = listen('draw', () => { | |
let w, h, r, isRect = p.shape.length === 2 | |
w = r = p.shape[0] | |
if (isRect) { | |
h = p.shape[1] | |
} | |
push() | |
alpha(p.opacity) | |
translate(p.pos.x, p.pos.y) | |
if (isRect) { | |
translate(w/2, h/2) | |
} | |
rotate(deg2rad(p.angle)) | |
scale(p.scale.x, p.scale.y) | |
if (isRect) { | |
rectfill(-w/2, -h/2, w, h, p.color) | |
} else { | |
circfill(0, 0, r, p.color) | |
} | |
pop() | |
}) | |
const endUpdate = listen('update', (dt) => { | |
velY += gravity * dt; | |
p.pos.x += velX * dt; | |
p.pos.y += velY * dt; | |
p.angle += velA * dt; | |
p.opacity -= fade * dt; | |
velX *= airDrag; | |
velY *= airDrag; | |
p.scale.x = wave(-1, 1, ELAPSED * spin); | |
p.lifespan -= dt; | |
if (p.lifespan <= 0) { | |
endUpdate() | |
endDraw() | |
} | |
}) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Demo