Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Confetti
class Progress {
constructor(param = {}) {
this.timestamp = null;
this.duration = param.duration || Progress.CONST.DURATION;
this.progress = 0;
this.delta = 0;
this.progress = 0;
this.isLoop = !!param.isLoop;
this.reset();
}
static get CONST() {
return {
DURATION : 1000
};
}
reset() {
this.timestamp = null;
}
start(now) {
this.timestamp = now;
}
tick(now) {
if (this.timestamp) {
this.delta = now - this.timestamp;
this.progress = Math.min(this.delta / this.duration, 1);
if (this.progress >= 1 && this.isLoop) {
this.start(now);
}
return this.progress;
} else {
return 0;
}
}
}
class Confetti {
constructor(param) {
this.parent = param.elm || document.body;
this.canvas = document.createElement("canvas");
this.ctx = this.canvas.getContext("2d");
this.width = param.width || this.parent.offsetWidth;
this.height = param.height || this.parent.offsetHeight;
this.length = param.length || Confetti.CONST.PAPER_LENGTH;
this.yRange = param.yRange || this.height * 2;
this.progress = new Progress({
duration : param.duration,
isLoop : true
});
this.rotationRange = typeof param.rotationLength === "number" ? param.rotationRange
: 10;
this.speedRange = typeof param.speedRange === "number" ? param.speedRange
: 10;
this.sprites = [];
this.canvas.style.cssText = [
"display: block",
"position: absolute",
"top: 0",
"left: 0",
"pointer-events: none"
].join(";");
this.render = this.render.bind(this);
this.build();
this.parent.appendChild(this.canvas);
this.progress.start(performance.now());
requestAnimationFrame(this.render);
}
static get CONST() {
return {
SPRITE_WIDTH : 9,
SPRITE_HEIGHT : 16,
PAPER_LENGTH : 100,
DURATION : 8000,
ROTATION_RATE : 50,
COLORS : [
"#EF5350",
"#EC407A",
"#AB47BC",
"#7E57C2",
"#5C6BC0",
"#42A5F5",
"#29B6F6",
"#26C6DA",
"#26A69A",
"#66BB6A",
"#9CCC65",
"#D4E157",
"#FFEE58",
"#FFCA28",
"#FFA726",
"#FF7043",
"#8D6E63",
"#BDBDBD",
"#78909C"
]
};
}
build() {
for (let i = 0; i < this.length; ++i) {
let canvas = document.createElement("canvas"),
ctx = canvas.getContext("2d");
canvas.width = Confetti.CONST.SPRITE_WIDTH;
canvas.height = Confetti.CONST.SPRITE_HEIGHT;
canvas.position = {
initX : Math.random() * this.width,
initY : -canvas.height - Math.random() * this.yRange
};
canvas.rotation = (this.rotationRange / 2) - Math.random() * this.rotationRange;
canvas.speed = (this.speedRange / 2) + Math.random() * (this.speedRange / 2);
ctx.save();
ctx.fillStyle = Confetti.CONST.COLORS[(Math.random() * Confetti.CONST.COLORS.length) | 0];
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.restore();
this.sprites.push(canvas);
}
}
render(now) {
let progress = this.progress.tick(now);
this.canvas.width = this.width;
this.canvas.height = this.height;
for (let i = 0; i < this.length; ++i) {
this.ctx.save();
this.ctx.translate(
this.sprites[i].position.initX + this.sprites[i].rotation * Confetti.CONST.ROTATION_RATE * progress,
this.sprites[i].position.initY + progress * (this.height + this.yRange)
);
this.ctx.rotate(this.sprites[i].rotation);
this.ctx.drawImage(
this.sprites[i],
-Confetti.CONST.SPRITE_WIDTH * Math.abs(Math.sin(progress * Math.PI * 2 * this.sprites[i].speed)) / 2,
-Confetti.CONST.SPRITE_HEIGHT / 2,
Confetti.CONST.SPRITE_WIDTH * Math.abs(Math.sin(progress * Math.PI * 2 * this.sprites[i].speed)),
Confetti.CONST.SPRITE_HEIGHT
);
this.ctx.restore();
}
requestAnimationFrame(this.render);
}
}
(() => {
const DURATION = 8000,
LENGTH = 120;
new Confetti({
width : window.innerWidth,
height : window.innerHeight,
length : LENGTH,
duration : DURATION
});
setTimeout(() => {
new Confetti({
width : window.innerWidth,
height : window.innerHeight,
length : LENGTH,
duration : DURATION
});
}, DURATION / 2);
})();
body {
background: #333;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.