Experimenting with the gooey effect - based on the work of Nadieh Bremer.
This is an attempt at an icon grid to circle transition.
license: mit |
Experimenting with the gooey effect - based on the work of Nadieh Bremer.
This is an attempt at an icon grid to circle transition.
<!DOCTYPE html> | |
<head> | |
<meta charset="utf-8"> | |
<script src="https://d3js.org/d3.v4.min.js"></script> | |
<style> | |
body { margin:0;position:fixed;top:0;right:0;bottom:0;left:0; } | |
.big-circle { | |
fill: none; | |
} | |
text { | |
fill: white; | |
font-family: sans-serif; | |
} | |
</style> | |
</head> | |
<body> | |
<script> | |
var margin = {top: 50, right: 50, bottom: 50, left: 50} | |
var width = 960 - margin.left - margin.right, | |
height = 500 - margin.top - margin.bottom; | |
var svg = d3.select("body").append("svg") | |
.attr("width", width + margin.left + margin.right) | |
.attr("height", height + margin.top + margin.bottom) | |
.append("g") | |
.attr("transform", "translate(" + [margin.left, margin.top] + ")") | |
var config = { | |
radius: 5, | |
gridLength: 10, | |
gridPadding: 10 | |
}; | |
var defs = svg.append('defs'); | |
var filter = defs.append('filter').attr('id','gooey'); | |
filter.append('feGaussianBlur') | |
.attr('in','SourceGraphic') | |
.attr('stdDeviation', config.radius * 1.8) | |
.attr('result','blur'); | |
filter.append('feColorMatrix') | |
.attr("class", "blurValues") | |
.attr('in','blur') | |
.attr('mode','matrix') | |
.attr('values', '1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 ' + config.radius +' -6') | |
.attr('result','gooey'); | |
filter.append("feBlend") | |
.attr("in", "SourceGraphic") | |
.attr("in2", "gooey") | |
.attr("operator", "atop"); | |
var data = d3.range(60).map(function(i) { | |
return { | |
r: config.radius, | |
colour: "purple" | |
} | |
}); | |
var simulation = d3.forceSimulation(data) | |
.force("x", d3.forceX(width / 2)) | |
.force("y", d3.forceY(height / 2)) | |
.force("collide", d3.forceCollide(config.radius + 1.5).iterations(2)) | |
.stop(); | |
var circleGroup = svg.append("g") | |
.style("filter", "url(#gooey)"); | |
var smallCircles = circleGroup.append("g").selectAll("circle") | |
.data(data) | |
.enter().append("circle") | |
.attr("class", "small-circle") | |
.attr("r", config.radius) | |
.attr("cx", (d, i) => d.x = (i % config.gridLength) * (config.gridPadding + config.radius * 2)) | |
.attr("cy", (d, i) => d.y = Math.floor(i / config.gridLength) * (config.gridPadding + config.radius * 2)) | |
.attr("fill", d => d.colour); | |
var bigCircle = circleGroup.append("g") | |
.append("circle") | |
.attr("class", "big-circle") | |
.attr("cx", width / 2) | |
.attr("cy", height / 2) | |
.attr("r", 0) | |
.style("fill", "purple"); | |
function clusterDots() { | |
// Interpolate between gooey filter and no gooey filter | |
transitionGoo(3000); | |
for (var i = 0; i < 120; ++i) simulation.tick(); | |
d3.selectAll(".small-circle") | |
.transition() | |
.duration(1500) | |
.delay((d,i) => calculateDistance(d, [width/2, height/2]) * 30) | |
.attr("cx", d => d.x) | |
.attr("cy", d => d.y) | |
d3.select(".big-circle") | |
.transition() | |
.delay(700) | |
.duration(2500) | |
.attr("r", Math.sqrt(data.length) * 1.5 * config.radius); | |
} | |
function separateDots() { | |
transitionGooBack(2000); | |
d3.select(".big-circle") | |
.transition() | |
.duration(2100) | |
.attr("r", 0); | |
d3.selectAll(".small-circle") | |
.transition() | |
.duration(1500) | |
.delay((d,i) => 1500 + (config.radius - calculateDistance(d, [width/2, height/2])) * 30) | |
.attr("cx", (d, i) => (i % config.gridLength) * (config.gridPadding + config.radius * 2)) | |
.attr("cy", (d, i) => Math.floor(i / config.gridLength) * (config.gridPadding + config.radius * 2)); | |
} | |
function loop() { | |
setTimeout(clusterDots, 1000); | |
setTimeout(separateDots, data.length * 150); | |
} | |
loop(); | |
setInterval(loop, data.length * 200); | |
function transitionGoo(duration) { | |
d3.selectAll(".blurValues") | |
.transition().duration(duration) | |
.attrTween("values", function() { | |
return d3.interpolateString("1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 6 -6", | |
"1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 " + config.radius * 8 + " -6"); | |
}); | |
} | |
function transitionGooBack(duration) { | |
d3.selectAll(".blurValues") | |
.transition().duration(duration) | |
.attrTween("values", function() { | |
return d3.interpolateString("1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 " + config.radius * 8 + " -6", "1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 6 -6"); | |
}); | |
} | |
function calculateDistance(d, point) { | |
return Math.sqrt(Math.pow(point[0] - d.x, 2) + Math.pow(point[1] - d.y, 2)) | |
} | |
</script> | |
</body> |