|
<!DOCTYPE html> |
|
<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="//cdn.rawgit.com/gka/d3-jetpack/master/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> |