Skip to content

Instantly share code, notes, and snippets.

@luizbills
Created March 28, 2025 15:40
Show Gist options
  • Save luizbills/361e21560cf91bc135d5976edef2a73c to your computer and use it in GitHub Desktop.
Save luizbills/361e21560cf91bc135d5976edef2a73c to your computer and use it in GitHub Desktop.
Confetti emitter for Litecanvas
// 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()
}
})
}
}
@luizbills
Copy link
Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment