Skip to content

Instantly share code, notes, and snippets.

@mbostock
Last active August 10, 2024 04:03
Show Gist options
  • Save mbostock/3916621 to your computer and use it in GitHub Desktop.
Save mbostock/3916621 to your computer and use it in GitHub Desktop.
Path Tween
license: gpl-3.0

This example demonstrates path interpolation using SVG's getPointAtLength feature. The source path and the target path are sampled uniformly using a configurable precision in pixels (here, 4px). During the transition, the path is replaced with a piecewise linear curve, or polyline, for easier interpolation. When the transition finishes, the path is restored to the original cubic Bézier. This technique is handy because it works for any path expressible with SVG's path data mini-language.

See also the circular shape tweening example.

<!DOCTYPE html>
<svg width="960" height="500">
<path transform="translate(180,150)scale(2,2)" fill="none" stroke="black" stroke-width="1.5"></path>
</svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
var d0 = "M0,0c100,0 0,100 100,100c100,0 0,-100 100,-100",
d1 = "M0,0c100,0 0,100 100,100c100,0 0,-100 100,-100c100,0 0,100 100,100";
d3.select("path")
.attr("d", d0)
.transition()
.duration(2000)
.on("start", function repeat() {
d3.active(this)
.attrTween("d", pathTween(d1, 4))
.transition()
.attrTween("d", pathTween(d0, 4))
.transition()
.on("start", repeat);
});
function pathTween(d1, precision) {
return function() {
var path0 = this,
path1 = path0.cloneNode(),
n0 = path0.getTotalLength(),
n1 = (path1.setAttribute("d", d1), path1).getTotalLength();
// Uniform sampling of distance based on specified precision.
var distances = [0], i = 0, dt = precision / Math.max(n0, n1);
while ((i += dt) < 1) distances.push(i);
distances.push(1);
// Compute point-interpolators at each distance.
var points = distances.map(function(t) {
var p0 = path0.getPointAtLength(t * n0),
p1 = path1.getPointAtLength(t * n1);
return d3.interpolate([p0.x, p0.y], [p1.x, p1.y]);
});
return function(t) {
return t < 1 ? "M" + points.map(function(p) { return p(t); }).join("L") : d1;
};
};
}
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment