SVG Toggle Hack

Aren't SVG buttons awesome? You click, you see a great animation and everyone has a warm, fuzzy, happy feel. One thing has been bugging me - toggling. An SVG animation can be started by a click event just by adding an atrribute to the SVG itself. To toggle an animation (say a 'like' button) you either have to rely on fancy css tranisitons or some scripting plus maybe a library (many thanks to @sarahsouheiden for the tip to try out greensock).

After much playing around I managed to hack my way to a button that toggles without the need for any scripting. Here is a breakdown of the thought process (nicely sanitised without the dead-ends and stupid mistakes along the way).

Start with an SVG animation you want to toggle

For this article the starting point is a plus button that turns into a tick. The pen below shows the starting point, the end point and a looping animation between the two states. The button consists of a circle element with two path elements. The animation moves the paths into a single point in the centre and then out into a cross or a tick.

Starting an animation on click

In the pen below the circle in the middle is what we want to animate. For now we have two circles on either side which will be used to control the central animation. Triggering an animation on a click can be done by adding following attribute:

begin="click"

This triggers an animation on the element it is attached to. An animation on a different element can be triggered at the same time. The circle on the left (labelled start) has an animation that for now doesn't do anything, that begins on a click and has an id.

id="startAnimation"

For the centre circle the looping seen in the first pen is split up into a forward and reverse animation . We begin the forward animation at the same time as the startAnimation.

begin="startAnimation.begin"

We do exactely the same for the reverse circle. Set up an animation that doesn't do anything, give it an id and start the reverse animation for the centre circle when it is clicked.

Check out the pen below - click on the start circle, then on the reverse and watch the central animation toggle.

Stopping the forward animation being repeated

Great! We pretty much have a toggling animation. However, if you click on the left circle twice in a row then we get the same forward animation. Also (you may be ahead of me here and have got to this already!) we do not want two circles either side but layer them on top of the button. How do we ensure that the correct circle is clicked on?

The solution here is to make those animations within the start and reverse circles do something. In this case we want the circle clicked on to disappear by changing the radius to 0.

For the other circle we want to set the inital radius as 0 so you can't click it. Just as we did for the main animation we will trigger an animation that changes its radius from 0 to 100 when the other button is clicked.

We will then do exactly the same but in reverse for the reverse part of the toggle. Hopefully the pen below will make things a bit clearer. Have a play clicking on the start and stop buttons and watch them shrink and grow.

Bringing it all together

The final step is to bring it all together - literally! Instead of the control circles being either side we put them on top of the centre element. The order is important here - the controls should be last so that they are on top of the pile and will register the click event.

As amazing as those control circles are, we don't want them hiding our animation. By setting fill-opacity="0" they will be hidden from view.

With SVG (or CSS) animations the general rule of thumb is whatever speed you think is ok, the actual speed should be faster. The controls should shrink in size almost instantly. dur=0.01s seems pretty quick to me. The other animations have also been given a bit of a nudge.

The final tweak was to add an extra animation on the button circle that shrinks the radius slightly so it looks like it is being clicked. Check out the finished pen - toggle away to your hearts content!

Conclusion

Great we have a working toggle button. We can amend the reverse animation so that the untoggle maybe does something slightly different. By chaining together as many of these animations we can have multi state options. In the real world a library such as greensock will have more power and flexibility but this hack provides a fun and interesting way of toggling an SVG animation as well as learning more about how SVG animations work.

Issues

This works great here but what issues could there be in the real world? The two that I can think of are:

You can only have one id on a page. If this element is repeated then when you click on the first element on the page the id of that animation will trigger all the svg elements! A solution would be dynamically creating the ids on the SVG (I had a play with $index in an angular repeator and it didn't work for me). Alternatively does anyone know if you can namespace the ids in an SVG so they don't interfere with each other?

The second issue is that you can't set the initial state - it is always a cross. Some scripting could be used or the whole structure could be recreated but with a different starting point.

Any other issues that spring to mind?

Thanks

I've loved playing with SVG over the past few months. What has made it especially enjoyable is the codepen community. Special thanks to @AmeliaBR, @sarahsouheiden and @chriscoyier for tips and comments along the way.

I struggled to find any articles or pens that solved the toggle issue like this but that doesn't mean they don't exisit. If you have a similar, different and probably much better solution it would be great to hear from you.