356 lines
13 KiB
JavaScript
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);
|
|
}
|