var ƒ = d3.f var width = innerWidth var height = innerHeight var s = 15 var v = .01 var fallSpeed = 10 var discardNum = 10 var blue = '#F44336' var red = '#2196F3' var cols = d3.range(width/s + 1).map(function(i){ return d3.range((innerHeight - 200)/s).map(function(j){ return { x: i*s, y: j*s + 50 + (i % 2 ? s/2 : 0), v: Math.random(), grouped: true, isRed: (i % 2 ? Math.random()*5 : Math.random()*.6) < .5 } }) }) var canvas = d3.select('body').html('') .append('canvas').at({width, height}) .on('mousemove', function(){ var pos = d3.mouse(this) discardNum = d3.scaleLinear().domain([0, width]).range([0, 15])(pos[0]) fallSpeed = d3.scaleLinear().domain([0, height]).range([0, 20])(pos[1]) }) .node() var ctx = canvas.getContext('2d') if (window.fallTimer) fallTimer.stop() window.fallTimer = d3.timer(function(t){ var squares = _.flatten(cols) var meanY = d3.mean(squares.filter(ƒ('grouped')), ƒ('y')) var dist = meanY - height/2 v += -dist/100000000000 v = clamp(-dist/3, v, dist/3) ctx.clearRect(0, 0, width, height) ctx.fillStyle = 'rgba(0,0,0,.3)' squares.forEach(function(d){ ctx.beginPath() d.y += d.grouped ? v : fallSpeed ctx.fillStyle = d.isRed ? red : blue var l = s/Math.sqrt(3) - 1 var dx = Math.sqrt(l*l - s*s/12)*.9 var x = d.x, y = d.y ctx.lineTo(d.x, y) ctx.lineTo(x += l, y += 0) ctx.lineTo(x += dx, y += s/2*.9) ctx.lineTo(x -= dx, y += s/2*.9) ctx.lineTo(x -= l, y += 0) ctx.lineTo(x -= dx, y -= s/2*.9) ctx.fill() }) cols.forEach(function(d, i){ if (!d[0].grouped) d[0].grouped = d[0].y - d[1].y + s + fallSpeed > -1 if (d[0].grouped) d[0].y = d[1].y - s if (_.last(d).y > height) d.pop() }) var colsByTop = d3.nestBy(cols, d=> d[0].y) var topCols = _.maxBy(colsByTop, d=> +d.key) var colsByBot = d3.nestBy(cols.filter(d=> _.last(d).grouped), d=> _.last(d).y) var botCols = _.maxBy(colsByBot, d=> +d.key) d3.range(Math.floor(discardNum)).forEach(function(){ var updateCol = topCols && topCols[Math.random()*topCols.length >> 0] if (updateCol && updateCol[0].grouped){ updateCol.unshift({ x: updateCol[0].x, y: -s, v: fallSpeed, grouped: false, isRed: Math.random() < .9 ? updateCol[0].isRed : Math.random() < .5 }) } var removeCol = botCols && botCols[Math.random()*botCols.length >> 0] var lastBox = removeCol && removeCol[removeCol.length - 1] if (lastBox && removeCol.length > 4 && lastBox.grouped){ lastBox.grouped = false lastBox.v = fallSpeed } }) }) function clamp(a, b, c){ return Math.max(a, Math.min(b, c)) }