Skip to content

Instantly share code, notes, and snippets.

@jacqui
Last active June 18, 2017 17:00
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jacqui/0710f7eb8fbe585d1b4734776d98c545 to your computer and use it in GitHub Desktop.
Save jacqui/0710f7eb8fbe585d1b4734776d98c545 to your computer and use it in GitHub Desktop.
animated svg on scroll
license: mit

Pop open in new, full screen window to see the line get drawn as you scroll, connecting the points.

Built with blockbuilder.org

<html>
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.10.1/lodash.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.6/d3.js" charset="utf-8"></script>
<style>
* {
font-family: Helvetica, Verdana, sans-serif;
}
.marker {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 400px;
border: 1px solid #ccc;
}
</style>
</head>
<body>
<svg width="1000" height="20000"></svg>
<div class="marker"></div>
<script>
var data = {categoryX: 300, points: [
{"x":300,"y":53.46},{"x":300,"y":64.33},{"x":300,"y":81.25},{"x":200,"y":169.69},{"x":300,"y":169.81},{"x":300,"y":209.83},{"x":300,"y":258.2},{"x":100,"y":352.41},{"x":300,"y":365.26},{"x":300,"y":437.33},{"x":300,"y":556.63},{"x":300,"y":589.71},{"x":300,"y":626.93},{"x":300,"y":649.84},{"x":300,"y":666.02},{"x":300,"y":689.28},{"x":300,"y":705.31},{"x":300,"y":727.3},{"x":300,"y":744.38},{"x":300,"y":766.83},{"x":300,"y":817.81},{"x":300,"y":862.84},{"x":400,"y":882.63},{"x":300,"y":1036.55},{"x":300,"y":1192.58},{"x":300,"y":1419.56},{"x":300,"y":1566.87},{"x":300,"y":8667.14},{"x":300,"y":8780.15},{"x":300,"y":1784.1},{"x":300,"y":1827.76},{"x":300,"y":1855.53},{"x":300,"y":1890.78},{"x":300,"y":1939.6},{"x":300,"y":1997.51},{"x":300,"y":1169.01},{"x":300,"y":2314.37},{"x":300,"y":2399.17},{"x":300,"y":2594.91},{"x":500,"y":2756.56},{"x":200,"y":3109.02}]};
var svg = d3.select('svg');
var distancePath = svg.append('path')
.attr('fill', 'none')
.attr('stroke', 'none').node();
var source;
var target;
var gap = 100;
var totalDistance = 0;
_.each(data.points, function(target) {
if (!source) {
target.d = 'M' + target.x + ',' + target.y;
} else {
if (source.y > target.y - gap) {
// if they're sufficiently close to each other
if (source.x === target.x) {
target.d = drawLine(target.x, target.y);
setDistance(source, target);
} else {
target.d = drawCurve(source.x, source.y, target.x, target.y);
setDistance(source, target);
target.y1 = target.y - source.y;
target.interpolate1 = d3.interpolate(0, target.distance);
}
} else {
target.d = '';
if (source.x !== data.categoryX) {
// if the source repo owner isn't the same as the contributor, move the line back
target.d += drawCurve(source.x, source.y, data.categoryX, source.y + gap / 3);
setDistance(source, target);
target.y1 = gap / 3;
target.interpolate1 = d3.interpolate(0, target.distance);
}
x = target.x;
y = target.y;
if (target.x !== data.categoryX) {
x = data.categoryX;
y = target.y - gap / 3;
}
target.d += drawLine(x, y);
target.y2 = y - (target.y1 || 0);
setDistance(source, target);
if (target.x !== data.categoryX) {
target.d += drawCurve(x, y, target.x, target.y);
var currentDistance = target.distance;
setDistance(source, target);
target.y3 = gap / 3;
target.interpolate2 = d3.interpolate(0, target.distance - currentDistance);
}
}
target.totalDistance = (totalDistance += target.distance);
}
source = target;
});
var path = svg.append('path')
.attr('d', _.pluck(data.points, 'd').join(' '))
.attr('fill-opacity', 0)
.attr('stroke', '#000000')
.attr('stroke-width', 2)
.attr('stroke-linecap', 'round')
.attr('stroke-dasharray', totalDistance)
.attr('stroke-dashoffset', totalDistance);
var circle = svg.selectAll('circle')
.data(data.points).enter().append('circle')
.attr('fill', '#fff')
.attr('stroke', '#000000')
.attr('stroke-width', 1)
.attr('cx', function(d) {return d.x})
.attr('cy', function(d) {return d.y})
.attr('r', 4);
window.addEventListener('scroll', _.throttle(windowScroll, 200));
function windowScroll() {
var top = scrollY + 400;
var source;
var target = _.find(data.points, function(point) {
if (point.y >= top) {
return true;
}
source = point;
return false;
});
if (source && target) {
var distance = 0;
var distanceFromSource = top - source.y;
if (!target.interpolate1) {
// if there's no interpolate1
if (!target.interpolate2) {
// and there's no interpolate2, must mean it's a straight line
distance = distanceFromSource + source.totalDistance;
} else {
// if there's a interpolate2, must mean there's a straight line
// and then a curve at the end, so figure out if we're in straight line or curve part
if (distanceFromSource <= target.y2) {
// it's in straight line part
distance = distanceFromSource + source.totalDistance;
} else {
// if it's in last curve part, first interpolate the curve
// and then add that back to the straight part and the previous total distance
var partialDistance = (distanceFromSource - target.y2) / target.y3;
distance = target.interpolate2(partialDistance) + target.y2 + source.totalDistance;
}
}
} else {
// if there's interpolate1, must mean there's a first curve
if (distanceFromSource <= target.y1) {
// so if it's within the first curve, interpolate that and add it to total distance
var partialDistance = distanceFromSource / target.y1;
distance = target.interpolate1(partialDistance) + source.totalDistance;
} else if (distanceFromSource <= (target.y1 + target.y2)) {
// if we're in line part, add curve to it
distance = target.interpolate1(1) + (distanceFromSource - target.y1) + source.totalDistance;
} else if (interpolate2) {
var partialDistance = (distanceFromSource - target.y2 - target.y1) / target.y3;
distance = target.interpolate1(1) + target.y2 + target.interpolate2(partialDistance);
}
}
// var partialDistance = (top - source.y) / (target.y - source.y);
// var distance = target.interpolater(partialDistance) + source.totalDistance;
path.transition().duration(200)
.attr('stroke-dashoffset', totalDistance - (distance || 0));
}
};
function drawLine(x, y) {
return 'L' + x + ',' + y;
}
function drawCurve(x1, y1, x2, y2) {
var cy = (y1 + y2) / 2;
return 'C' + x1 + ',' + cy + ' ' + x2 + ',' + cy + ' ' + x2 + ',' + y2;
}
function setDistance(source, target) {
var distancePathD = 'M' + source.x + ',' + source.y + ' ' + target.d;
distancePath.setAttribute('d', distancePathD);
target.distance = parseFloat(distancePath.getTotalLength().toFixed(2));
}
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment