Files
www-geburtstag/confetti.js
2020-02-16 14:23:46 +01:00

356 lines
13 KiB
JavaScript

// utilities
function getLength(x0, y0, x1, y1) {
// returns the length of a line segment
const x = x1 - x0;
const y = y1 - y0;
return Math.sqrt(x * x + y * y);
}
function getDegAngle(x0, y0, x1, y1) {
const y = y1 - y0;
const x = x1 - x0;
return Math.atan2(y, x) * (180 / Math.PI);
}
// some constants
const DECAY = 4; // confetti decay in seconds
const SPREAD = 30*3.141592/180; // degrees to spread from the angle of the cannon
const GRAVITY = 120;
class ConfettiCannon {
constructor() {
// setup a canvas
this.canvas = document.getElementById('canvas');
this.dpr = window.devicePixelRatio || 1;
this.ctx = this.canvas.getContext('2d');
this.ctx.scale(this.dpr, this.dpr);
// add confetti here
this.confettiSpriteIds = [];
this.confettiSprites = {};
// vector line representing the firing angle
this.drawVector = false;
this.vector = [{
x: window.innerWidth * 0.9,
y: window.innerHeight * 0.8,
}, {
x: window.innerWidth,
y: window.innerHeight,
}];
this.pointer = {};
// bind methods
this.render = this.render.bind(this);
this.handleMousedown = this.handleMousedown.bind(this);
this.handleMouseup = this.handleMouseup.bind(this);
this.handleMousemove = this.handleMousemove.bind(this);
this.handleTouchstart = this.handleTouchstart.bind(this);
this.handleTouchmove = this.handleTouchmove.bind(this);
this.setCanvasSize = this.setCanvasSize.bind(this);
this.setupListeners();
this.setCanvasSize();
// fire off for a demo
//this.timer = setTimeout(this.handleMouseup, 1000);
}
setupListeners() {
// Use TweenLite tick event for the render loop
TweenLite.ticker.addEventListener('tick', this.render);
// bind events
window.addEventListener('mousedown', this.handleMousedown);
window.addEventListener('mouseup', this.handleMouseup);
window.addEventListener('mousemove', this.handleMousemove);
window.addEventListener('touchstart', this.handleTouchstart);
window.addEventListener('touchend', this.handleMouseup);
window.addEventListener('touchmove', this.handleTouchmove);
window.addEventListener('resize', this.setCanvasSize);
}
setCanvasSize() {
this.canvas.width = window.innerWidth * this.dpr;
this.canvas.height = window.innerHeight * this.dpr;
this.canvas.style.width = window.innerWidth + 'px';
this.canvas.style.height = window.innerHeight + 'px';
}
handleMousedown(event) {
clearTimeout(this.timer);
const x = event.clientX * this.dpr;
const y = event.clientY * this.dpr;
this.vector[0] = {
x,
y,
};
this.drawVector = true;
}
handleTouchstart(event) {
clearTimeout(this.timer);
event.preventDefault();
const x = event.touches[0].clientX * this.dpr;
const y = event.touches[0].clientY * this.dpr;
this.vector[0] = {
x,
y,
};
this.drawVector = true;
}
handleMouseup(event) {
this.drawVector = false;
const x0 = this.vector[0].x;
const y0 = this.vector[0].y;
const x1 = this.vector[1].x;
const y1 = this.vector[1].y;
const length = getLength(x0, y0, x1, y1);
const angle = (getDegAngle(x0, y0, x1, y1) - 180)*-3.141592/180;
const particles = length / 3 + 5;
const velocity = length * 50;
this.addConfettiParticles(particles, angle, velocity, x0, y0);
}
handleMousemove(event) {
const x = event.clientX * this.dpr;
const y = event.clientY * this.dpr;
this.vector[1] = {
x,
y,
};
this.pointer = this.vector[1];
}
handleTouchmove(event) {
event.preventDefault();
const x = event.changedTouches[0].clientX * this.dpr;
const y = event.changedTouches[0].clientY * this.dpr;
this.vector[1] = {
x,
y,
};
this.pointer = this.vector[1];
}
drawVectorLine() {
this.ctx.strokeStyle = 'pink';
this.ctx.lineWidth = 2 * this.dpr;
const x0 = this.vector[0].x;
const y0 = this.vector[0].y;
const x1 = this.vector[1].x;
const y1 = this.vector[1].y;
this.ctx.beginPath();
this.ctx.moveTo(x0, y0);
this.ctx.lineTo(x1, y1);
this.ctx.stroke();
}
addConfettiParticles(amount, angleP, velocityP, x, y) {
let i = 0;
while (i < amount) {
// sprite
const r = _.random(4, 6) * this.dpr;
const d = _.random(15, 25) * this.dpr;
const cr = _.random(30, 255);
const cg = _.random(30, 230);
const cb = _.random(30, 230);
const color = `rgb(${cr}, ${cg}, ${cb})`;
const tilt = _.random(10, -10);
const tiltAngleIncremental = _.random(0.07, 0.05);
const tiltAngle = 0;
const id = _.uniqueId();
const friction = _.random(0.10, 0.25);
const minAngle = angleP - SPREAD / 2;
const maxAngle = angleP + SPREAD / 2;
const minVelocity = velocityP / 2;
const maxVelocity = velocityP;
// Physics Props
const velocity = _.random(minVelocity, maxVelocity);
const angle = _.random(minAngle, maxAngle);
const sprite = {
[id]: {
angle,
velocity,
x,
y,
r,
d,
color,
tilt,
tiltAngleIncremental,
tiltAngle,
friction
},
};
Object.assign(this.confettiSprites, sprite);
this.confettiSpriteIds.push(id);
//this.tweenConfettiParticle(id);
i++;
}
}
tweenConfettiParticle(id) {
const minAngle = this.confettiSprites[id].angle - SPREAD / 2;
const maxAngle = this.confettiSprites[id].angle + SPREAD / 2;
const minVelocity = this.confettiSprites[id].velocity / 4;
const maxVelocity = this.confettiSprites[id].velocity;
// Physics Props
const velocity = _.random(minVelocity, maxVelocity);
const angle = _.random(minAngle, maxAngle);
const gravity = GRAVITY;
const friction = _.random(0.01, 0.025);
const d = 0;
/*TweenLite.to(this.confettiSprites[id], DECAY, {
physics2D: {
velocity,
angle,
gravity,
friction,
},
d,
ease: Power4.easeIn,
onComplete: () => {
// remove confetti sprite and id
_.pull(this.confettiSpriteIds, id);
delete this.confettiSprites[id];
},
});*/
}
updateConfettiParticle(id) {
const sprite = this.confettiSprites[id];
const tiltAngle = 0.0005 * sprite.d;
//sprite.angle -= 0.01;
var vx = Math.cos(sprite.angle)*sprite.velocity;
var vy = -Math.sin(sprite.angle)*sprite.velocity;
vx = Math.sign(vx)*Math.max(0, Math.abs(vx) - sprite.friction);
vy = vy + GRAVITY - sprite.friction;
sprite.angle = Math.atan2(-vy, vx);
sprite.tiltAngle += tiltAngle;
sprite.tiltAngle += sprite.tiltAngleIncremental;
sprite.tilt = (Math.sin(sprite.tiltAngle - (sprite.r / 2))) * sprite.r * 2;
sprite.y += vy*0.002+Math.sin(sprite.r / 2);//+Math.sin(sprite.angle + sprite.r / 2);
sprite.x += vx*0.002;//Math.cos(sprite.angle) * 2;
}
drawConfetti() {
var deletable = [];
this.confettiSpriteIds.map(id => {
const sprite = this.confettiSprites[id];
this.ctx.beginPath();
this.ctx.lineWidth = sprite.d / 2;
this.ctx.strokeStyle = sprite.color;
this.ctx.moveTo(sprite.x + sprite.tilt + sprite.r, sprite.y);
this.ctx.lineTo(sprite.x + sprite.tilt, sprite.y + sprite.tilt + sprite.r);
this.ctx.stroke();
this.updateConfettiParticle(id);
});
}
drawPointer() {
const centerX = this.pointer.x;
const centerY = this.pointer.y;
const radius = 15 * this.dpr;
this.ctx.beginPath();
this.ctx.arc(centerX, centerY, radius, 0, 2 * Math.PI, false);
this.ctx.fillStyle = 'transparent';
this.ctx.fill();
this.ctx.lineWidth = 2 * this.dpr;
this.ctx.strokeStyle = '#ffffff';
this.ctx.stroke();
}
drawPower() {
const x0 = this.vector[0].x;
const y0 = this.vector[0].y;
const x1 = this.vector[1].x;
const y1 = this.vector[1].y;
const length = getLength(x0, y0, x1, y1);
const centerX = x0;
const centerY = y0;
const radius = 1 * this.dpr * length / 20;
this.ctx.beginPath();
this.ctx.arc(centerX, centerY, radius, 0, 2 * Math.PI, false);
this.ctx.fillStyle = 'transparent';
this.ctx.fill();
this.ctx.lineWidth = 2 * this.dpr;
this.ctx.strokeStyle = '#333333';
this.ctx.stroke();
}
render() {
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
// only draw the vector when the drawVector flag is on
this.drawVector && this.drawVectorLine();
this.drawVector && this.drawPower();
this.drawPointer();
this.drawConfetti();
}
fire(x1, y1, x2, y2) {
this.vector = [{
x: x1,
y: y1,
}, {
x: x2,
y: y2,
}];
this.handleMouseup();
}
}
var isMobile = false; //initiate as false
// device detection
if(/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|ipad|iris|kindle|Android|Silk|lge |maemo|midp|mmp|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino/i.test(navigator.userAgent)
|| /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(navigator.userAgent.substr(0,4))) isMobile = true;
if (!isMobile) {
const confetti = new ConfettiCannon();
setTimeout(() => {
confetti.fire(window.innerWidth * 0.9, window.innerHeight * 0.8, window.innerWidth, window.innerHeight);
}, 1000);
setTimeout(() => {
confetti.fire(window.innerWidth * 0.1, window.innerHeight * 0.8, 0, window.innerHeight);
}, 1050);
setTimeout(() => {
confetti.fire(window.innerWidth * 0.9, window.innerHeight * 0.8, window.innerWidth, window.innerHeight);
}, 2000);
setTimeout(() => {
confetti.fire(window.innerWidth * 0.1, window.innerHeight * 0.8, 0, window.innerHeight);
}, 2050);
}