// 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); }