// http://www.paulirish.com/2011/requestanimationframe-for-smart-animating/ window.requestAnimFrame = (function(){ return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || function( callback ){ window.setTimeout(callback, 1000 / 60); }; })(); // PLAY WITH THESE VARS var numCircles = 50, maxDelay = 80, colorFrom = [0, 100, 100, .5], // rgba colorTo = [250, 250, 250, 0]; // rgba var canvas = document.querySelector('canvas'), ctx = canvas.getContext('2d'), circles = [], destX = 0, destY = 0, automate = true, automateRadius, automatePosition = 0, winW, winH, circleMaxWidth, circleMaxHeight; // fire it all up function init() { reset(); initEvents(); loop(); } // reset stage function reset() { setupStage(); createCircles(); } // setup & clear canvas function setupStage() { winW = window.innerWidth; winH = window.innerHeight; automateRadius = winH * .25; circleMaxWidth = Math.min(250, winW * .25); circleMaxHeight = circleMaxWidth * .25; canvas.width = winW; canvas.height = winH; ctx.clearRect(0, 0, canvas.width, canvas.height); } // bind events function initEvents() { canvas.addEventListener('mousemove', mouseMoveHandler); canvas.addEventListener('mouseenter', mouseEnterHandler); canvas.addEventListener('mouseleave', mouseLeaveHandler); window.addEventListener('resize', reset); } // track mouse position function mouseMoveHandler(e) { destX = e.pageX; destY = e.pageY; } // disable automatic movement function mouseEnterHandler(e) { automate = false; } // enable automatic movement function mouseLeaveHandler(e) { automate = true; } // create the circles function createCircles() { // global var circles circles = []; var midX = winW >> 1, midY = winH >> 1, i = numCircles, step = 1 / numCircles, delay, scale, width, height, color, c; while (i--) { // properties for the circle scale = step * i + step; delay = maxDelay * scale; width = circleMaxWidth * scale; height = circleMaxHeight * scale; color = getColor(i); c = new Circle(midX, midY, width, height, delay, color); circles.push(c); } } function Circle(x, y, width, height, delay, color) { this.x = x; this.y = y; this.destX = x; this.destY = y; this.width = width; this.height = height; this.delay = delay; this.color = color; } // update position // todo: implement Penner's easing functions Circle.prototype.update = function(destX, destY) { this.destX = destX; this.destY = destY; this.x += (this.destX - this.x) / this.delay; this.y += (this.destY - this.y) / this.delay; } // draw a circle on the canvas Circle.prototype.draw = function() { ctx.lineWidth = 3; ctx.strokeStyle = 'rgba(' + this.color.join(',') + ')'; // http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas ctx.save(); ctx.beginPath(); ctx.translate(this.x - this.width, this.y - this.height); ctx.scale(this.width, this.height); ctx.arc(1, 1, 1, 0, 2 * Math.PI, false); ctx.restore(); ctx.stroke(); } // get a color for a circle based on it's index (0 - numCircles) function getColor(index) { var stepR = (colorTo[0] - colorFrom[0]) / (numCircles - 1), red = Math.round(colorFrom[0] + (stepR * index)); var stepG = (colorTo[1] - colorFrom[1]) / (numCircles - 1), green = Math.round(colorFrom[1] + (stepG * index)); var stepB = (colorTo[2] - colorFrom[2]) / (numCircles - 1), blue = Math.round(colorFrom[2] + (stepB * index)); var stepA = (colorTo[3] - colorFrom[3]) / (numCircles - 1), alpha = colorFrom[3] + (stepA * index); return [red, green, blue, alpha]; } // paint it function loop() { requestAnimationFrame(loop); ctx.globalCompositeOperation = "source-over"; ctx.fillStyle = "rgba(1, 1, 1, .8)"; ctx.fillRect(0, 0, canvas.width, canvas.height); ctx.globalCompositeOperation = "lighter"; var i = circles.length, c; if (automate === true) { destX = winW >> 1; destY = (winH >> 1) + (Math.sin(automatePosition) * automateRadius); automatePosition += .02; } while (i--) { c = circles[i]; c.update(destX, destY); c.draw(); } } init();