Animating a complex SVG icon with dash-array and dash-offset in CSS
There are many ways SVG and CSS can play together, especially when we’re trying to make things move online. From shapes and colors, we can transform various attributes of our shape to give our pages some life. Here I’ll investigate how we can deal with the strokes of our SVG paths, using primarily the dash-array and dash-offset proprieties.
Before we dive into animation or even how to construct our SVG image, let’s take a look at stroke-dasharray :
In it’s most basic form, stroke-dasharray is defined as a length, here’s it’s set to 5 which means that the size of the dashes will be 5px, and the gaps between the dashes will also be 5px.
It’s also possible to specify a different size for the dashes and the gap, by passing 2 values as you’ll see below :
On the first line, the 5 10 value means the dashes will be 5px long, and the gaps 10px wide. Same principle for the second line with 100px long dashed and 5px wide gaps.
The third line is a bit trickier. The stroke-dasharray end value is always even, so whenever an uneven number is passed it’s repeated twice, so in this examples, 30 20 10 actually becomes 30 20 10 30 20 10, meaning our first dash is 30px long, followed by a 20px gap, a 10px dash and the size start over, but with a gap that’s 30px wide, followed by a 20px dash and a 10px gap, and then the whole suite starts over. Hope that makes sense ! If you recall the first example, where only 5 was defined, it was actually computed as 5 5.
When giving a uniteless value, the stroke-dasharray assumes we’re talking pixels. This is all well and good but we often use SVG because we want scalable elements, fluid sizes and responsive layouts… The good news is that we can also use percentage as shown above to define the size of our dashes, to make it much more manageable.
So far we only used the stroke-dasharray attribute of the SVG line, but we can also set it directly from out CSS code, as shown above.
We just a few lines of code now, we can make things move by using usual CSS Animation proprieties :
Now that we know how to split our stroke in multiple dashes and gaps, let’s take a look at the stroke-dashoffset propriety. It’s used to specific the offset between the start of the SVG path and the initial dash.
Here’s a demo, the stroke-dasharray is set to 5% in the CSS and only the stroke-dashoffset is defined for each line in the SVG.
If you’re looking closely, you’ll notice something a bit strange. How come the first and third line look the same, despite the stroke-dashoffset for both lines being respectively 0 and 20 ?
That’s because the stroke-dasharray propriety defines a pattern that repeats itself through the whole stroke of the element. On our lines, it goes from the left to the right, but if we move the pattern along using the offset, it’s kind of infinite to the left too.
Play around the the example above if that doesn’t really make sense right now, it’s a bit tricky at first, but it’s basically like manipulating the background-position of an element with a repeating background.
Again, the value can either be uniteless, and it’ll be computed as pixels, or a percentage.
To go through everything properly, let’s also animate that, just because we can :
Simple enough, the offset animates nicely.
Alright, now that we know how we’ll be animating things, let’s take a look at a cool design to get a feel for the kind of SVG elements w’ll need.
I stumbled upon this cool design on Dribbble and it’s a perfect example of the technique we just learnt !
We’ll be animating the transition between the two states of the button, the default state and the close state.
I’ll be using a slightly different design to prevent any kind of issue as I don’t own the right to this great design, but I wanted to give it full credit for giving me the idea !
So anyway, here are the elements we’ll need in our SVG file :
- The top line, that turns into one of the lines in the close state
- The bottom line, that turns into the other line of the close state
- The middle line that blends into the full border of the button
The first two elements are fairly straighforward and shouldn’t need any explanation. The middle line is the key element of our whole design : we’ll make a single element for both the middle line and the border and use the 2 CSS proprieties we just learnt about to transition between the two states.
A cool designer has worked with did something for me in just a few secondes, here’s the result.
Looking back at the animation above, we know that most of our work will have to do with animating the middle line-border stroke : at first the first dash will be small and the offset will be null, then we’ll increase the dash length to make the border appear, and then we’ll increase the offset to make the middle line disappear…
And that’s where a little warning should fire off in your mind : due to the fact that the stroke-dasharray is a pattern, not the fixed size of a given dash or gap, it’ll be a bit tricky to manage that last part of the animation… How will be ensure that we don’t have some infinite leftover coming from the offset ?
That’s where a little trick comes into play : there can’t be an offset issue if there’s no offset…
Okay that’s a bit mysterious, I’ll explain : instead of using stroke-dashoffset, we’ll have our initial stroke-dasharray start with 0% 0%… This means that the first dash will be 0% width, then the first gap will be non-existant as well, and then the first dash will have the required length. At the end of our animation, when the middle line disappears, all we’ll have to do is keep the first dash at 0%, but move the first gap to the needed size to make the middle line disappear !
No infinite pattern issue, less things to think about !
Okay, let’s start by coding a simple animation to go from both our states without any user interaction, just to get he right values for our dasharray.
It takes a bit of tinkering but in the end we can find the right values… I did encounter a little but though : the first part of the path would always show, despite the 0% initial dash size… I quickly fixed that with a stroke-dasharray of 0.1% to prevent it from showing without messing up my other values.
Cool, now let’s add some JavaScript and a close class to our SVG, so that it goes from a state to another dynamically instead. Now all I need to do is use the second state of my animation as the stroke-dassharray value of my close state and add a css transition on this same propriety so it goes smoothly from one to the other.
Cool ! It’s starting to look like what we’re trying to achieve !
Okay now we add some transforms to the top and bottom line to make the close icon, that’s fairly easy we simple rotate them and translate them so they go where we need them… One little thing we need to do is change the transform-origin propriety for both lines to set it to center center, so it’s easier to make sense of how they move…
This animation gets messy though, as everything happens at the same time. Using proper transition-delay timings, we can order things better. One key thing to look out for is that we can define different delays when we’re in default and close state, but it’s a bit counter-intuitive : in the default state we set the delays for the transition from close to default, and for the closed state we set the transition from default to close… It always takes me a few secondes to wrap my head around that… It just doesn’t make sense to me, though it’s kind of logical when you think about it…
Anyway, things are taking shape now, we have our initial state, when clicked we animate to the close state with our cool border animation…
Let’s now see how we can break it all !
Cross browser test
When trying to break things, the easiest way to do it is to try it in various browsers. I’m coding in Google Chrome, so I’ll start with Firefox… And things go poorly already : Everything works fine but one tiny detail : the transition on the stroke-dasharray doesn’t seem to work, it goes directly from one state t another…
Things break in Safari too, but not for the same reason : here’s it’s the top and bottom line that aren’t placed properly. That shouldn’t be too difficult to fix on it’s own but it’s weird that it’s not behaving like Chrome and FF… Something to look into !
Last but not least for the desktop browser, IE11 shows pretty much the same behavior as FF…
Alright, so let’s see how we could fix that !
My first hunch turned out to be the right one for Firefox : going from a uniteless value to a percentage-based value during the animation was a bit too much to handle, so all I had to do was for the various stroke-dasharray to replace the 0 by 0% and everything animated properly ! I wish everything was always this easy !
It may not look like it, but this could be a problem though : most CSS minifiers change any value set to 0 with a unit to a uniteless 0… That makes sense, since 0 is always 0 but clearly here some browsers will behave differently so it’ll be important to either have a specific setting here, or isolate this specific bit of code !
responsive sizing
Other example that could use the same technique
Follow me on Twitter !