Alternate illustrative concept for Businessweek’s Max Chafkin & Brian Womack’s April 2016 feature on Yahoo. Move the mouse to fling the flyers around. Flyers are accelerated in the direction the mouse is moving, proportionate to their proximity. Then there’s air friction and gravity, so the flyers resume an equilibrium downward path along the torus.
When I say “torus” I just mean that it wraps around at the top/bottom and left/right edges, which topologically is a torus, right? But I dropped topology class after like a week. Also, gravity’s a vector, so easily configurable!!!!! I should really use a little vector math library. I-should-buy-a-boat.jpg.
Flyer (& whole cover & feature design) by Tracy Ma.
To-do:
xxxxxxxxxx
<html>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title></title>
<style>
* {
box-sizing: border-box;
}
html, body {
margin: 0;
padding: 0;
overflow-x: hidden;
font-family: sans-serif;
}
.opener {
position: fixed;
z-index: 1;
padding: 5em;
width: calc(100% + 2 * 133px);
height: calc(100% + 2 * 133px);
margin-left: -133px;
margin-top: -133px;
}
.opener img.flyer {
position: absolute;
z-index: 1;
top: -1000px;
max-height: 133px;
max-width: 133px;
}
</style>
<body>
<div class="opener"></div>
</body>
<script src="//cdnjs.cloudflare.com/ajax/libs/d3/3.5.6/d3.min.js" charset="utf-8"></script>
<script src="https://cdn.jsdelivr.net/npm/d3-jetpack@1.0.2/d3-jetpack.js" charset="utf-8"></script>
<script src="timerControl.js" charset="utf-8"></script>
<script>
Number.prototype.mod = function(n) {
return ((this%n)+n)%n;
};
driftingFlyers(d3.select('.opener'));
function driftingFlyers(container) {
var mousemoves = [];
var mouseSpeed = 0;
var mouseVector = [0,0];
container.on('mousemove', function() {
mousemoves.push({t: Date.now(), pt: d3.mouse(this)});
if(mousemoves.length > 100) mousemoves.shift();
});
var gravity = [0, .1];
var frictionCoef = 0.01;
var mouseEffectScale = d3.scale.linear()
.domain([0,700])
.range([1,0])
.clamp(true);
var flyersData = d3.range(20).map(function(d) {
return {
x: container.node().offsetWidth * Math.random(),
y: container.node().offsetHeight * Math.random(),
r: Math.PI/2,
dx: rand(10),
dy: 5,
dr: rand(Math.PI/32)
}
})
var flyer = container.selectAll('img.flyer')
.data(flyersData)
.enter()
.append('img.flyer')
.attr('src', 'flyer.png');
var driftFlyersTimer = new TimerControl(function(t) {
// calculate mouse velocity vector
var now = Date.now();
var mouseSamples = mousemoves.filter(function(d) { return now - d.t < 400 });
if(mouseSamples.length > 1) {
var s0 = mouseSamples[0];
var s1 = mouseSamples[mouseSamples.length-1];
mouseSpeed = distance(s1.pt, s0.pt) / (s1.t - s0.t);
if(isNaN(mouseSpeed)) mouseSpeed = 0; // ugh this is bad, it's more like infinity but...
mouseVector = normalize(s1.pt, s0.pt).map(function(d) { return d * mouseSpeed; });
if(isNaN(mouseVector[0])) mouseVector = [0,0];
} else {
mouseSpeed = 0;
mouseVector = [0,0];
}
// animate falling flyers
container.selectAll('img.flyer')
.each(function(d,i) {
if(mousemoves.length > 1) {
var distanceToMouse = distance([d.x, d.y], mousemoves[mousemoves.length-1].pt);
d.dx += mouseVector[0] * mouseEffectScale(distanceToMouse);
d.dy += mouseVector[1] * mouseEffectScale(distanceToMouse);
}
d.dx += gravity[0];
d.dy += gravity[1];
var frictionVector = [d.dx, d.dy].map(function(d) {
return -d * frictionCoef;
});
d.dx += frictionVector[0];
d.dy += frictionVector[1];
d.x += d.dx;
d.y += d.dy;
d.r += d.dr;
d.x = d.x.mod(container.node().offsetWidth);
d.y = d.y.mod(container.node().offsetHeight);
})
.style('left', function(d) { return d.x + 'px'; })
.style('top', function(d) { return d.y + 'px'; })
.style('transform', function(d) { return 'rotate(' + d.r + 'rad)'; });
});
driftFlyersTimer.play();
}
function rand(amplitude) {
return amplitude * (Math.random()-0.5);
}
function distance(a,b) {
var x = b[0] - a[0];
var y = b[1] - a[1];
return Math.sqrt(Math.pow(x,2) + Math.pow(y,2));
}
function difference(a,b) {
return d3.zip(a,b).map(function(x) {
return x.reduce(function(a, b) {
return a - b;
});
});
}
function normalize(a,b) {
return difference(a,b).map(function(x) {
return x/distance(a,b);
});
}
</script>
</html>
Modified //cdn.rawgit.com/gka/d3-jetpack/master/d3-jetpack.js to a secure url
https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.6/d3.min.js
https://cdn.rawgit.com/gka/d3-jetpack/master/d3-jetpack.js