Recently, there’s been a lot of buzz throughout the web design and development community on using SVG for icons. Due to a number of limitations and inconveniences, using raster-based images for your icons, has become an increasingly painful and restricting endevour.
There are a number of reasons why a raster based image for the purposes icons, have become obsolete. I covered this and explored a number of viable solutions in this article
here, which is worth reading if you are mostly unfamiliar with the use of SVG in web development. I explored a number of different methods of implementing icons including, pure CSS, icon fonts, and SVG. However a lot of people are still unfamiliar with SVG styling and implementation, as there isn’t yet a widely-accepted definitive way that works well.
Which brings us to this article. In this tutorial I will be showing you how to create, implement, animate and style SVG icons on the web using simple markup. I’m assuming, if you’ve made it this far, that you have at minimal, a basic understanding of CSS, HTML, a little JavaScript and Adobe Illustrator.
If you are completely unfamiliar with SVG, I would recommend reading
this article on SVG basics over at CSS-Tricks prior to going any further.
Let’s get started.
Prepping SVG
For the purposes of this article, we are going to be using a rocket icon (originally designed by
Pierre Borodin), although I slightly modified the icon and converted it to SVG. You can download the Adobe Illustrator file
here. On a side note, if you prefer to work with your own icon, feel free to do so, the basic methods will remain the same.
Open up the file in Illustrator, click “
Save As” and select “
.SVG“. Once you click save, the “
SVG Save Options” dialog will pop-up. You can then click on “
SVG Code” which will open the XML code for the file in a text editor.
This is a useful feature, however all the CSS that is used to style the SVG is embedded in “
inline” format within the various paths. This is a problem, as we won’t be able to do any custom styling or animation.
Separating all that CSS manually would be a pain, so we are going separate it automatically later. For now, go ahead and save the SVG using all the default save options.
To seperate all the CSS so it’s not inline we’ll use a free online
SVG Optimiser tool by Peter Collingridge. Upload the SVG you saved from Illustrator, check the “
convert styles to CSS” box and download the new SVG file. Open the file up in a text editor or whatever program you use to code.
You’ll notice that all the CSS styling is now separated from all SVG-path markup, which is good. You’ll also notice that there are a number of class’s and ID’s attached within the markup, similar to traditional HTML markup. Additionally you should see some “
<g></g>” elements.
The classes and IDs work pretty much the same as HTML and can be manipulated in CSS. In SVG <g> is a grouping element and works in a similar way as a”div” would in HTML. It’s worth noting that when you do your designing in Illustrator, a layer will be represented as a group (<g>) in SVG markup, which is all the more reason to name and organise your layering appropriately.
Cleaning Up SVG for Web Use
In order to use SVG’s to to their full potential, we need to clean a few things up. There are two things we need to do. Firstly – Illustrator’s back-end leaves all the grouping ID’s with arbitrary characters in their name. Secondly, the SVG optimizer we used, adds generic styling classes such as “
style1, style2, style 3“..etc. We need to go through the file and rename all the styles and IDs appropriately so we can work on them later.
Here I have cleaned up the SVG markup, which looks like this:
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="307px"
height="283px" viewBox="0 0 307 283" enable-background="new 0 0 307 283" xml:space="preserve">
<g class="rocket_wrap">
<circle cx="147.5" cy="138.6" r="105.5" class="icon_circle"/>
<g class="rocket_inner">
<g class="fire" id="fireMiddle">
<path class="fire_path" d="M148.891,179.906c3.928,0,7.111,3.176,7.111,7.094 c0,7.78-7.111,16-7.111,16s-7.111-8.349-7.111-16C141.78,183.082,144.963,179.906,148.891,179.906z"/>
</g>
<g class="fire" id="fireRight">
<path class="fire_path" d="M154.063,181.092c3.577-1.624,7.788-0.048,9.408,3.52 c3.216,7.084,0.139,17.508,0.139,17.508s-9.927-4.662-13.09-11.63C148.9,186.923,150.487,182.715,154.063,181.092z"/>
</g>
<g class="fire" id="fireLeft">
<path class="fire_path" d="M143.392,182.519c3.25,2.207,4.098,6.623,1.896,9.864 c-4.372,6.436-14.873,9.238-14.873,9.238s-1.191-10.902,3.108-17.23C135.725,181.149,140.143,180.312,143.392,182.519z"/>
</g>
<g class="fire" id="fireSmallLeft">
<path class="fire_path" d="M143.193 187.531c2.226 0.4 3.7 2.6 3.2 4.8 c-0.875 4.407-5.829 8.264-5.829 8.264s-3.09-5.53-2.229-9.865C138.807 188.5 141 187.1 143.2 187.531z"/>
</g>
<g class="fire" id="fireSmallRight">
<path class="fire_path" d="M152.089 188.599c2.043-0.985 4.496-0.132 5.5 1.9 c1.952 4 0.3 10.1 0.3 10.107s-5.795-2.56-7.713-6.541C149.186 192 150 189.6 152.1 188.599z"/>
</g>
<path class="rocket_bottom" d="M157.069 171.31h-3.292c-1.562-0.048-3.178-0.076-4.846-0.076 s-3.284 0.028-4.846 0.076h-3.292c-7.277-7.938-12.371-26.182-12.371-47.434c0-28.54 9.182-51.676 20.508-51.676 c11.327 0 20.5 23.1 20.5 51.676C169.44 145.1 164.3 163.4 157.1 171.31z"/>
<g id="right_wing_wrap">
<path class="wing_base" d="M166.678 127.161c0 0 17.7 3.3 12.9 48.099l-18.06-14.05 L166.678 127.161z"/>
<path class="wing_shadow" d="M158.225 140.336c10.481-5.584 22.7 22.2 21.4 34.9 l-18.06-14.05C161.542 161.2 156.1 144.3 158.2 140.336z"/>
</g>
<g id="left_wing_wrap">
<path class="wing_base" d="M135.131 161.21l-18.06 14.1 c-4.805-44.793 12.924-48.099 12.924-48.099L135.131 161.21z"/>
<path class="wing_shadow" d="M135.131 161.21l-18.06 14.1 c-1.367-12.746 10.896-40.509 21.377-34.924C140.614 144.3 135.1 161.2 135.1 161.21z"/>
</g>
<g id="rocket_body_wrap">
<path class="rocket_base" d="M162.728 167.358c-3.778-0.623-8.573-0.996-13.796-0.996 s-10.018 0.373-13.795 0.996c-5.033-10.186-8.257-25.808-8.257-43.338c0-30.688 9.873-55.566 22.052-55.566 s22.053 24.9 22.1 55.566C170.984 141.6 167.8 157.2 162.7 167.358z" />
<path class="rocket_shadow" d="M145.464 166.417c19.578-40.575 7.26-85.229 4.112-98.067 c11.88 0.9 21.4 25.4 21.4 55.525c0 17.529-3.225 33.152-8.257 43.337c0 0-3.786-0.472-8.069-0.697 S145.464 166.4 145.5 166.417z"/>
</g>
<g id="large_window_wrap">
<radialgradient id="SVGID_2_" cx="148.9" cy="112.5" r="15.2" fx="139.4853" fy="112.5239" gradientunits="userSpaceOnUse">
<stop offset="0" class="window_grandient"/>
<stop offset="0.5868" class="window_grandient"/>
<stop offset="0.6834" class="window_grandient"/>
<stop offset="0.6845" class="window_grandient1"/>
<stop offset="0.6861" class="window_grandient2"/>
<stop offset="0.6897" class="window_grandient3"/>
</radialgradient>
<circle class="large_window_path" cx="148.9" cy="111.3" r="10.5"/>
</g>
<circle class="small_window_path" cx="148.9" cy="132.4" r="5.2"/>
</g>
</g>
</svg>
Setting Up
We’re going to make 3 states for this icon – a inactive state, a hover state, and a toggled click state. We’ll use a combination of mainly CSS, with a tiny bit of JavaScript to achieve this. If you don’t need the click function and just want a hover state, you can animate everything using only CSS without including any JavaScript at all. However I thought we should cover the JavaScript anyway.
For the purposes of this tutorial, we’ll be writing all our markup inline, into one document. However if you’re working on a larger project, you can easily use PHP, JavaScript or simply an HTML “<object>” with the src set to the external SVG file to insert SVG markup into your pages.
Also, we won’t be adding all the browser prefixes (-webkit, -O-, etc..) to the CSS, purely for the benefit of not bloating the tutorial markup. However you should use the correct vendor prefixes to ensure browser compatibility. You can add browser prefixes automatically using
this prefixer from Nettuts+.
Setup a blank HTML document and paste your SVG code into the body, remove the CSS from SVG markup and place it in the head of document. You can actually delete the default styles from the document altogether, as we we’ll be custom styling the icon ourselves.
<!DOCTYPE html><head><title>SVG Icon Animation</title><style>/* SVG Styles Go Here */</style></head><body><div id="pageWrap"><svg"><!-- SVG Mark up --></svg></div></body></html>
Styling SVG with CSS
Now let’s start to style our SVG markup. Before we worry about animating the icon, let’s just focus on making the hover and inactive states. We’ll start with the inactive state:
/*=============================================
[ Page Setup ]
==============================================*/
body{background:#34495e}
#pageWrap {width:100%;overflow:hidden;}
#rocket{display:block;margin:0 auto;margin-top:150px;}
/*=============================================
[ Inactive State Styles ]
==============================================*/
.rocket_inner {
transform: translateY(15px) translateX(-3px);
transition: .3s;
}
.icon_circle {
transition: .2s;
fill: #22303e;
}
.large_window_path {
transition: .2s;
fill: #22303e;
}
.small_window_path {
transition: .2s;
fill: #22303e;
}
.wing_shadow {
fill: #34495e;
}
.rocket_bottom { fill: #34495e }
.rocket_base { fill: #34495e }
.rocket_shadow { fill: #34495e }
.window_grandient { stop-color: #2DCB73 }
.window_grandient1 { stop-color: #2AC16D }
.window_grandient2 { stop-color: #29B968 }
.window_grandient3 { stop-color: #28B767 }
.wing_base { fill: #34495e }
.fire_path { fill: #FC0 }
.fire {display: none;}
As you can see, there are a number of elements we have styled using
SVG styling markup for CSS. You can get more intricate with your styling, however you mostly only need to use the fill, stroke and stroke-width attributes. You’ll notice there’s a gradient fill as well, which was setup in Illustrator. This makes a reflection-gradient for the top window on the rocket once it’s animated.
If you load your page in the browser now, your icon should look like this:
Now into the hover effect. SVG class’s and IDs act as you would expect them to in regards to CSS. We can add a number of hover effects using
attributes.
/*=============================================
[ Hover Styles ]
==============================================*/
.rocket_wrap:hover .icon_circle {
fill: #34495e;
stroke: #fff;
stroke-width: 3px;
}
.rocket_wrap:hover .rocket_base {
fill: #fff;
stroke: #fff;
stroke-width: 3px;
}
.rocket_wrap:hover .small_window_path {
fill: #34495e;
stroke: #fff;
stroke-width: 2px;
}
.rocket_wrap:hover .rocket_bottom {
fill: #fff;
stroke: #fff;
}
.rocket_wrap:hover .wing_base {
fill: #fff;
stroke: #fff;
stroke-width: 3px;
}
.rocket_wrap:hover .large_window_path {
fill: #34495e;
stroke: #fff;
stroke-width: 2px;
}
.rocket_wrap:hover .wing_shadow { display: none }
.rocket_wrap:hover .rocket_shadow { fill: #fff }
Here we are basically changing the fill and stroke colors along with the stroke widths. It’s relatively straight forward and mostly self-explanatory. You can play around with all kinds of things like opacity, sizes, stroke caps and more.
Load your page again and hover on/off the icon. It should change states. Pretty cool right?
Animating SVG With CSS
There are a number of JavaScript plugins to animate SVG, and I expect these will develop further and become more user-friendly in the future. However you can achieve most effects using simple CSS and CSS keyframes. If you’re not familiar with CSS keyframes, Chris Coyier has a good intro article
here on CSS-Tricks.
If you don’t want any JavaScript, you can use CSS “
@keyframe” animation properties using simple “
:hover” effects. However we will use a little bit of JavaScript to toggle them on click. We’ll get to that later though, for now let’s just set-up the keyframes and click classes.
We’ll setup our animation through 3 sets of classes. The SVG paths that don’t require animation but simply need to change color, will have a clicked class. Then for the elements that need to be animated, we’ll add a clicked class which contains the animation name of an “
@keyframe” animation. Then we’ll apply them to their respective elements with a “.
click” function using JavaScript.
Let’s look at the static elements that just need a color change:
/*=============================================
[ Clicked Styles ]
==============================================*/
.baseClicked {
fill: #FFFFFF !important;
stroke-width: 0px !important;
}
.shadowClicked {
fill: #EDEDED !important;
stroke-width: 0px !important;
}
.iconCircleClicked {
fill: #282e3a !important;
stroke: #fff !important;
stroke-width: 7px !important;
}
.smallWindowClicked {
fill: #28B767 !important;
stroke-width: 0px !important;
}
.wingShadowClicked {
display: block !important;
fill: #FC9252 !important;
}
.wingBaseClicked {
fill: #E16E36 !important;
stroke-width: 0px !important;
}
.rocketBottomClicked {
fill: #2DCB73 !important;
stroke-width: 0px !important;
}
.largeWindowClicked {
fill: url(#SVGID_2_) !important;
stroke-width: 0px !important;
}
.innerClicked { transform: translateY(0px) translateX(-3px) !important; }
These are our classes that we’ll append to the icon’s various elements later. Again, these are mostly self explanatory changes to fill and stroke attributes. You’ll notice however that they all have an “!important” declaration. This resolves the issue of CSS hover effects being displayed while JavaScript click classes are active by overriding the “:hover” property.
Now for the animation of the fire. You’ll notice the SVG file has 5 fire flames at the bottom. By default these aren’t visible as we have made them invisible using CSS using “display:none” Each fire element has it’s own ID, while sharing the same class. This way we can display them all at once while animating them individually.
Let’s add our animation markup:
/*=============================================
[ Animation Classes ]
==============================================*/
.fire {
display: none;
animation-delay: 0s;
fill-opacity: 1;
animation-timing-function: ease-in;
stroke-width: 0px;
animation-iteration-count: infinite;
animation-timing-function: linear;
transform-origin: 50% 50%;
animation-direction: normal;
}
.clickedLeft {
display: block;
animation-delay: 0s;
animation-name: fireLeft, fillOpacity1;
animation-duration: 1.2s;
}
.clickedMiddle {
display: block;
animation-delay: 0s;
animation-name: fireMiddle, fillOpacity1;
animation-duration: 1s;
}
.clickedRight {
display: block;
animation-delay: 0s;
animation-name: fireRight, fillOpacity1;
animation-duration: 1.3s;
}
.clickedSmallLeft {
display: block;
animation-delay: 0s;
animation-name: fireSmall, fillOpacity2;
animation-duration: 1.3s;
transform-origin: bottom;
}
.clickedSmallRight {
display: block;
animation-delay: 0.3s;
animation-name: fireSmall, fillOpacity3;
animation-duration: 1.6s;
transform-origin: bottom;
}
Notice we have two “animation-name“s for each element, one for fill-opacity and another for transforms. The reason we’re not using a single animation for every element is so it they all animate in slightly different ways. If they all shared a single keyframe animation, the result would look automated an unnatural. If you don’t want to use any JavasSript, you can add these classes as CSS “:hover” effects using the regular mark-up classes.
Let’s setup the keyframes:
/*=============================================
[ KeyFrame Animations ]
==============================================*/
@keyframes fireSmall {
10% { transform: rotate(17deg) translateY(1px) }
20% { transform: rotate(-13deg) translateY(2px) }
30% { transform: rotate(21deg) translateY(3px) }
40% { transform: rotate(-34deg)translateY(4px) }
50% { transform: rotate(24deg) translateY(5px) }
60% { transform: rotate(-17deg) translateY(6px) }
70% { transform: rotate(31deg) translateY(7px) }
80% { transform: rotate(-28deg) translateY(8px) }
90% { transform: rotate(14deg) translateY(9px) }
99% { transform: rotate(0deg) translateY(10px) }
}
@keyframes fireLeft {
6% { transform: rotate(25deg) }
15% { transform: rotate(-19deg) }
25% { transform: rotate(25deg) }
32% { transform: rotate(-30deg) }
46% { transform: rotate(22deg) }
54% { transform: rotate(-29deg) }
61% { transform: rotate(32deg) }
74% { transform: rotate(-9deg) }
83% { transform: rotate(16deg) }
99% { transform: rotate(0deg) }
}
@keyframes fireMiddle {
10% { transform: rotate(25deg) }
20% { transform: rotate(-25deg) }
30% { transform: rotate(30deg) }
40% { transform: rotate(-22deg) }
50% { transform: rotate(29deg) }
60% { transform: rotate(-45deg) }
70% { transform: rotate(37deg) }
80% { transform: rotate(-15deg) }
90% { transform: rotate(16deg) }
99% { transform: rotate(0deg) }
}
@keyframes fireRight {
15% { transform: rotate(17deg) }
23% { transform: rotate(-13deg) }
37% { transform: rotate(21deg) }
45% { transform: rotate(-34deg) }
54% { transform: rotate(24deg) }
67% { transform: rotate(-17deg) }
72% { transform: rotate(31deg) }
84% { transform: rotate(-28deg) }
96% { transform: rotate(14deg) }
99% { transform: rotate(0deg) }
}
@keyframes fillOpacity1 {
0% {
fill-opacity: 1;
stroke-opacity: 1;
}
50% {
fill-opacity: 1;
stroke-opacity: 1;
}
100% {
fill-opacity: 0;
stroke-opacity: 0;
}
}
@keyframes fillOpacity2 {
0% {
fill-opacity: 1;
stroke-opacity: 1;
}
25% {
fill-opacity: 1;
stroke-opacity: 1;
}
100% {
fill-opacity: 0;
stroke-opacity: 0;
}
}
@keyframes fillOpacity3 {
0% {
fill-opacity: 1;
stroke-opacity: 1;
}
67% {
fill-opacity: 1;
stroke-opacity: 1;
}
100% {
fill-opacity: 0;
stroke-opacity: 0;
}
}
@keyframes rocektMove {
0% { transform: translateY(0px) }
100% { transform: translateY(20px) }
}
This sets-up our keyframing for the fire animation as well as the rocket itself. CSS uses percentages to set each keyframe at a percentage of the total animation, which is defined in time using the “
animation-duration:” attribute.
Applying CSS Classes to SVG using JavaScript
This ins’t something that is easy to achieve, there are a number of complications to consider.
Firstly jQeury selectors don’t work on SVG markup. So you can’t select a class/ID and add/toggle a CSS class the way you normally would. Fortunately Keith Wood has built a number of
JavaScript plugins specifically for this (and for other SVG/jQuery related issues). We need to include his plugin so our JavaScript can read the SVG DOM. Head over and download the jQuery plugin
here and add the “
jquery.svg.js” and “
jquery.svgdom.js” files to your document folder, as well as including a jQuery library.
Insert the following in the head of your document below the CSS.
<script src="jquery.min.js"></script>
<script src="jquery.svg.js"></script>
<script src="jquery.svgdom.js"></script>
Now we can access the SVG DOM using JavaScript selectors and add our CSS animation classes. Add the following below the SVG markup of the document:
<script type="text/javascript">
$(".rocket_wrap").click(function() {
$("#fireLeft").toggleClass("clickedLeft");
$("#fireMiddle").toggleClass("clickedMiddle");
$("#fireRight").toggleClass("clickedRight");
$("#fireSmallRight").toggleClass("clickedSmallRight");
$("#fireSmallLeft").toggleClass("clickedSmallLeft");
$(".rocket_inner").toggleClass("innerClicked");
$(".rocket_base").toggleClass("baseClicked");
$(".rocket_shadow").toggleClass("shadowClicked");
$(".large_window_path").toggleClass("largeWindowClicked");
$(".small_window_path").toggleClass("smallWindowClicked");
$(".rocket_bottom").toggleClass("rocketBottomClicked");
$(".wing_base").toggleClass("wingBaseClicked");
$(".wing_shadow").toggleClass("wingShadowClicked");
$(".icon_circle").toggleClass("iconCircleClicked");
});
</script>
Again, this is pretty self explanatory. If you’re not that familiar with jQuery, we are setting a click function for the
rocket icon, then using a toggle function to add/remove the CSS animation classes. As we noted above, the “!impotant” deceleration in CSS will override the hover state when these are added.
There are some considerations here though. Most notably that you can only access inline, internal SVG DOMs with jQeury. If you use a data attribute in an <object> for an externally stored SVG file, the jQuery won’t work. However, as I noted above there are a number of solutions to this issue depending on what you’re working with. I have included some links in the additional info section at the bottom of this article.
An Update On Firefox (Updated 23.12.2013)
After playing around with a number of CSS properties regarding the animation of SVG, I realised that Firefox does not interpret the CSS attribute “
transform-origin:” when animating SVG groups (<g>). Regardless of whether you prefix it, or where you put it (either in the @keyframe or on the group selector). So “
-moz-transform-origin:” won’t work, no matter what.
However it does work on SVG paths. If you’re not rotaing anything, this probably won’t cause an issue. However to ensure browser compatibility, you should use ID and class selectors on the SVG path elements, not groups. Hopefully this get’s fixed some time soon by the Moz dev team.
The only issue this causes is that animating a group of paths with the same transform origin can be fickle, as you need to go through each path element and set their individual transform origins to the same point relative to each path (rather than just grouping them).
Summary
So as you can see, we are able to animate SVGs with simple CSS “
@keyframe” animations. The demo above shows the animation with no jQuery at all. Having said that, this is just one way to animate SVG using simple markup. There are already a number of jQuery libraries, plugins and other useful web apps that you can use. I’ve included a number of useful links below.
On a final note, one thing I’d like to add is the best thing about animating an icon this way and probably the biggest advantage over using a third-party plugin, is that you can include all your CSS styling and animation mark-up directly in the SVG file itself (rather than in your page’s stylesheet). This is seriously the best thing about it, as it doesn’t bloat or effect existing mark-up. Furthermore, if you do a lot of work for clients, once you’ve set up all your icons, you can keep all the animations set-up in the SVG, which makes it really easy and efficient. Simply use a html “<object>” with the source to your SVG and all the animation and styling is ready to go!
I hope you’ve enjoyed this tutorial.
Additional Info
Here are some useful SVG related links:
What are your thoughts on animating SVG? Are you new to CSS keyframing? Let us know your thoughts in the comments below.