Skip to content

Instantly share code, notes, and snippets.

@brianally
Last active September 23, 2015 01:58
Show Gist options
  • Save brianally/b477ac1e7b4cabc8eb0b to your computer and use it in GitHub Desktop.
Save brianally/b477ac1e7b4cabc8eb0b to your computer and use it in GitHub Desktop.
Stroke Dash CSS Transition

One of Mike Bostock's examples shows how a line can be made to appear to be animated as its drawn to the screen using a trick with the CSS stroke-dasharray property. You can see it in action here.

As usual with d3, the code is very concise and easy to understand. (OK, not all d3 code is easy to understand but still.) However, i wondered whether the transition could be applied with CSS. Not completely, of course, as we require the length of the line to set up the initial dasharray. But what if we wanted to set the transition time in our CSS?

Sure, it's a bit ridiculous, but let's just pretend our workflow involves designers handling all the styles. Or, we have a component that does this one thing and we don't want to have to set the timing in our JS, even if just to pass it in as an int. Maybe we're anal about having presentation logic in our JS.

Yeah, i know--it's SVG.

So, right away i figured it should be easy enough to declare the stroke-dashoffset transition in a style rule, and another class rule where stroke-dashoffset is set to 0. Create the line, get its length to set up the dasharray/offset, then simply add the class to trigger the transition. Boom.

svg.append("path")
    .attr("d", line);

var l = path.node().getTotalLength();

path
	.attr("stroke-dasharray", l + " " + l)
	.attr("stroke-dashoffset", l);

path.classed("computed", true);

And the CSS:

path {
	fill        : none;
	stroke      : #000;
	stroke-width: 3px;	
	transition  : stroke-dashoffset 7.5s linear;
}

path.computed {
  stroke-dashoffset: 0;
}

Why "computed"? I started off with "animate" for want of anything better. But, whatever (sometimes absurd) attempts i made, i could never get that line to animate. Instead, that line just plopped onto the screen fully-formed. It seemed that there was no way to append the path in one state, add a class to apply another state, and then see the transition do its thing. And then i came across the ingenious solution here. The answer is to call window.getComputedStyle() to flush the pending style updates to the DOM.

In the example here i've followed Mike's lead by packaging together the relevant code to keep things tidy. It should be simple enough to create a plugin with this. However, there's a big caveat to placing the transition call in CSS: It can't be repeated. But, if you've got a project that involves placing a lot of paths which you'd like to see animated (once) with different durations, and you'd like to organise your durations in a separate CSS file, this might be for you.

NOTE

I have no idea how this compares to d3's transitions. I didn't set out to improve on that at all. In fact, i wouldn't be surprised if it is less performant to run this with CSS than JS. I certainly wasn't looking to replace transition() as it's got many other uses besides making a magic drawing line. I just had an itch that needed scratching.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Stroke Dash CSS Transition</title>
<style>
path {
fill: none;
stroke: #000;
stroke-width: 3px;
/* added */
transition: stroke-dashoffset 7.5s linear;
}
path.computed {
stroke-dashoffset: 0;
}
</style>
</head>
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script>
<script>
var points = [
[480, 200],
[580, 400],
[680, 100],
[780, 300],
[180, 300],
[280, 100],
[380, 400]
];
var line = d3.svg.line()
.tension(0) // Catmull–Rom
.interpolate("cardinal-closed");
var svg = d3.select("body").append("svg")
.datum(points)
.attr("width", 960)
.attr("height", 500);
svg.append("path")
.style("stroke", "#ddd")
.style("stroke-dasharray", "4,4")
.attr("d", line);
svg.append("path")
.attr("d", line)
.call(animateStroke);
function animateStroke(path) {
var l = path.node().getTotalLength();
path
.attr("stroke-dasharray", l + " " + l)
.attr("stroke-dashoffset", l);
// flush style queue; credit:
// https://timtaubert.de/blog/2012/09/css-transitions-for-dynamically-created-dom-elements/
window.getComputedStyle(path.node()).getPropertyValue("stroke-dashoffset");
// trigger transition
path.classed("computed", true);
}
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment