var util = { random : function(minOrMax, max) { if(arguments.length === 1) { return minOrMax * Math.random(); } return minOrMax + (Math.random() * (max - minOrMax)); }, normalise : function(value, min, max) { return (value - min) / (max - min); }, between : function(value, min, max) { return (value > min) && (value < max); }, inBounds : function (x, y, width, height) { return this.between(x, 0, width) && this.between(y, 0, height); }, invoke : function(fn, args) { return function(obj) { return obj[fn].apply(obj, args); }; } }; function fluent(fn) { return function() { fn.apply(this, arguments); return this; }; } function Vector (x, y) { this.x = x || 0; this.y = y || 0; } Vector.prototype = { constructor : Vector, getX : function() { return this.x; }, setX : fluent(function(x) { this.x = x; }), getY : function() { return this.y; }, setY : fluent(function(y) { this.y = y; }), getAngle : function() { return Math.atan2(this.y, this.x); }, setAngle : fluent(function(angle) { var magnitude = this.getMagnitude(); this.x = Math.cos(angle) * magnitude; this.y = Math.sin(angle) * magnitude; }), getMagnitude : function() { return Math.sqrt( Math.pow(this.x, 2) + Math.pow(this.y, 2) ); }, setMagnitude : fluent(function(magnitude) { var angle = this.getAngle(); this.x = Math.cos(angle) * magnitude; this.y = Math.sin(angle) * magnitude; }) }; Vector.add = function(a, b) { return new Vector( a.getX() + b.getX(), a.getY() + b.getY() ); }; Vector.subtract = function(a, b) { return new Vector( a.getX() - b.getX(), a.getY() - b.getY() ); }; Vector.multiply = function(vector, value) { return new Vector( vector.getX() * value, vector.getY() * value ); }; Vector.divide = function(vector, value) { return new Vector( vector.getX() / value, vector.getY() / value ); }; function Particle (x, y, speed, direction) { this.position = new Vector(x, y); this.velocity = new Vector().setMagnitude(speed).setAngle(direction); } Particle.prototype = { constructor : Particle, update : fluent(function() { this.position = Vector.add(this.position, this.velocity); }), accelerate : fluent(function(acceleration) { this.velocity = Vector.add(this.velocity, acceleration); }), getPositionX : function() { return this.position.getX(); }, setPositionX : fluent(function(x) { this.position.setX(x); }), getPositionY : function() { return this.position.getY(); }, setPositionY : fluent(function(y) { this.position.setY(y); }) }; function Canvas (elem, width, height) { this.width = elem.width = (width * Canvas.pixelRatio); this.height = elem.height = (height * Canvas.pixelRatio); elem.style.height = "100%"; elem.style.width = "100%"; this.context = elem.getContext("2d"); this.context.scale(Canvas.pixelRatio, Canvas.pixelRatio); this.setSmoothing(false); } Canvas.prototype = { constructor : Canvas, setSmoothing : fluent(function(value) { this.context.webkitImageSmoothingEnabled = value; this.context.mozImageSmoothingEnabled = value; this.context.msImageSmoothingEnabled = value; this.context.imageSmoothingEnabled = value; }), clear : fluent(function(width, height) { width = width || this.width; height = height || this.height; this.context.clearRect(0, 0, width, height); }), rect : fluent(function(x, y, width, height) { x = x || 0; y = y || 0; width = width || this.width; height = height || this.height; this.context.beginPath(); this.context.rect(x, y, width, height); }), circle : fluent(function (x, y, radius) { this.context.beginPath(); this.context.arc(x, y, radius, Math.PI * 2, false); }), fill : fluent(function(color) { color = color || "#000000"; this.context.fillStyle = color; this.context.fill(); }), stroke : fluent(function(color) { color = color || "#000000"; this.context.strokeStyle = color; this.context.stroke(); }) }; Canvas.pixelRatio = window.devicePixelRatio || 1; var NUM_PARTICLES = 40 / Canvas.pixelRatio; // make it easier for devices pushing more pixels var FADE = "rgba(255,255,255,0.008)"; function RandomParticle (x, y) { this.particle = new Particle(x, y, util.random(3, 15), util.random(Math.PI * 2)); this.color = RandomParticle.randomColor(); this.radius = util.random(3, 15); } RandomParticle.prototype = { constructor : RandomParticle, gravity : new Vector(0, 0.1), render : function(canvas) { this.particle.accelerate(this.gravity).update(); canvas .circle(this.particle.getPositionX(), this.particle.getPositionY(), this.radius) .fill(this.color); }, isVisible : function(width, height) { var x = this.particle.getPositionX(); var y = this.particle.getPositionY(); return util.inBounds(x, y, width, height); } }; RandomParticle.randomColor = function() { return "hsl(" + util.random(0, 360) + ", 75%, 60%)"; }; RandomParticle.createExplosion = function(x, y) { var explosion = []; for(var i = 0; i < NUM_PARTICLES; i++) { explosion.push(new RandomParticle(x, y)); } return explosion; }; window.onload = function() { debugger; var width = window.innerWidth; var height = window.innerHeight; var canvas = new Canvas(document.querySelector("#canvas"), width, height); var particles = RandomParticle.createExplosion(width / 2, height / 2); var renderParticle = util.invoke("render", [canvas]); var filterVisibleParticles = util.invoke("isVisible", [width, height]); function render() { if(particles.length) { canvas.rect().fill(FADE); particles.forEach(renderParticle); particles = particles.filter(filterVisibleParticles); } requestAnimationFrame(render); } render(); function explode(e) { e = e.touches ? e.touches[0] : e; particles = particles.concat(RandomParticle.createExplosion(e.clientX, e.clientY)); } document.addEventListener("click", explode); document.addEventListener("touchend", explode); var output = document.querySelector("output"); var slider = document.querySelector("#particles"); slider.value = output.innerHTML = NUM_PARTICLES; slider.addEventListener("input", function(e) { NUM_PARTICLES = output.innerHTML = this.valueAsNumber; }); };