const TWO_PI = Math.PI * 2; // canvas settings var viewWidth = 768, viewHeight = 768, backgroundCanvas = document.getElementById("background_canvas"), bctx, linesCanvas = document.getElementById("lines_canvas"), lctx, circlesCanvas = document.getElementById("circles_canvas"), cctx; var timeStep = (1/60), time = 0; var spacing = 32; var targets = [], trails = []; function initCanvasses() { backgroundCanvas.width = viewWidth; backgroundCanvas.height = viewHeight; bctx = backgroundCanvas.getContext('2d'); bctx.translate(spacing * 0.5, spacing * 0.5); linesCanvas.width = viewWidth; linesCanvas.height = viewHeight; lctx = linesCanvas.getContext('2d'); lctx.translate(0.5, 0.5); lctx.translate(spacing * 0.5, spacing * 0.5); circlesCanvas.width = viewWidth; circlesCanvas.height = viewHeight; cctx = circlesCanvas.getContext('2d'); cctx.translate(spacing * 0.5, spacing * 0.5); } function createBackground() { bctx.fillStyle = '#444'; for (var i = 0; i < viewWidth / spacing; i++) { for (var j = 0; j < viewWidth / spacing - (i % 2); j++) { var x = j * spacing + spacing * 0.5 * (i % 2), y = i * spacing; bctx.beginPath(); bctx.arc(x, y, 1, 0, Math.PI * 2); bctx.fill(); targets.push({x:x, y:y, ix:j, iy:i}); } } var p, n; for (var k = 0; k < targets.length; k++) { p = targets[k]; p.neighbors = []; n = getTargetByIndex(p.ix - 1, p.iy - 1); if (n !== null) p.neighbors.push(n); n = getTargetByIndex(p.ix, p.iy - 1); if (n !== null) p.neighbors.push(n); n = getTargetByIndex(p.ix + 1, p.iy - 1); if (n !== null) p.neighbors.push(n); n = getTargetByIndex(p.ix + 1, p.iy); if (n !== null) p.neighbors.push(n); n = getTargetByIndex(p.ix + 1, p.iy + 1); if (n !== null) p.neighbors.push(n); n = getTargetByIndex(p.ix, p.iy + 1); if (n !== null) p.neighbors.push(n); n = getTargetByIndex(p.ix - 1, p.iy + 1); if (n !== null) p.neighbors.push(n); n = getTargetByIndex(p.ix - 1, p.iy); if (n !== null) p.neighbors.push(n); } } function getTargetByIndex(ix, iy) { for (var i = 0; i < targets.length; i++) { if (targets[i].ix === ix && targets[i].iy === iy) { return targets[i]; } } return null; } function createTrails(){ var colors = ['#ff0', '#0ff', '#f0f']; var trail, start; for (var i = 0; i < 256; i++) { trail = new Trail(getRandom(colors)); start = getRandom(targets); trail.pos.x = start.x; trail.pos.y = start.y; trail.target = getRandom(start.neighbors); trail.lastTarget = start; trail.history.push(start); trails.push(trail); } } function update() { trails.forEach(function(t) { t.update(); }); } function draw() { cctx.clearRect(-spacing * 0.5, -spacing * 0.5, viewWidth, viewHeight); lctx.clearRect(-spacing * 0.5, -spacing * 0.5, viewWidth, viewHeight); trails.forEach(function(t) { t.draw(); }); } function getRandom(a) { return a[Math.floor(Math.random() * a.length)]; } window.onload = function() { initCanvasses(); createBackground(); createTrails(); requestAnimationFrame(loop); }; function loop() { update(); draw(); time += timeStep; requestAnimationFrame(loop); } Trail = function(color) { this.color = color; this.pos = new Point(); this.vel = new Point(); this.acc = new Point(); this.speed = 2e-3 + Math.random() * 2e-3; this.dir = new Point(); this.history = []; }; Trail.prototype = { update:function() { this.history.unshift(this.pos.x, this.pos.y); if (this.history.length > 256) this.history.length = 256; this.dir.copy(this.target).sub(this.pos); this.acc.copy(this.dir).mul(this.speed); this.vel.add(this.acc); if (this.dir.length() < this.vel.length()) { var t; this.pos.set(this.target.x, this.target.y); this.vel.set(0, 0); do { t = getRandom(this.target.neighbors); } while (t === this.lastTarget); this.lastTarget = this.target; this.target = t; } else { this.pos.add(this.vel); } }, draw:function() { lctx.strokeStyle = this.color; lctx.beginPath(); for (var i = 0; i < this.history.length; i += 2) { lctx.lineTo(this.history[i], this.history[i + 1]); } lctx.stroke(); cctx.fillStyle = this.color; cctx.beginPath(); cctx.arc(this.pos.x, this.pos.y, 2, 0, TWO_PI); cctx.fill(); } }; Point = function(x, y) { this.set(x, y); }; Point.prototype = { add:function(p) { this.x += p.x; this.y += p.y; return this; }, sub:function(p) { this.x -= p.x; this.y -= p.y; return this; }, mul:function(s) { this.x *= s; this.y *= s; return this; }, div:function(s) { this.x /= s; this.y /= s; return this; }, set:function(x, y) { this.x = x || 0; this.y = y || 0; return this; }, copy:function(p) { this.x = p.x; this.y = p.y; return this; }, length:function() { return Math.sqrt(this.x * this.x + this.y * this.y); }, max:function(v) { var l = this.length(); if (l > v) { this.div(l); this.mul(v); } return this; } };