Download this example on Github
39 |I don't know about you, but I was reluctant to start using SVGs in projects because I thought they were kinda confusing, there seemed to be so many ways to save them and mark them up, it seemed like a lot of work, and I assumed support was bad. Turns out support is good, there are solid fallbacks, and we can automate all of the tedious work and make working with SVGs downright easy!
42 |The Goals
45 |-
46 |
- Simplify saving SVGs for use 47 |
- Use SVGs in a way that allows us to style them with CSS 48 |
- Use SVGs without adding more HTTP requests (part of why we're using SVG in the first place) 49 |
- Simplify the markup 50 |
- Optimize our SVGs for Accessibility 51 |
- Provide PNG fallbacks for browser that don't support SVG (*without loading extra images for browsers that do) 52 |
- Don't waste time with tedious repetitive tasks 53 |
*Update (01.26.15)
55 |Filament Group created a Grunt task called Grunticon that does pretty much all of this and a bit more, but I wasn't using it because it didn't allow you to embed the SVG icons right into the page in order to be able to style them with CSS, but, Version 2.0 was just released and now includes the option to do just that! I haven't tested it out just yet but the demonstration looks great!
56 |How?
59 |We are going to use Grunt.js (if Gulp.js is your preferred flavor of automation, there are equivalent plugins to achieve the same things we'll be doing with Grunt) to run some of the tasks that help optimize our SVGs for use and accessibility, and streamline our workflow.
60 |There are three different Grunt plugins we are going to be utilizing, as well as a JavaScript plugin that will help us provide a fallback for browsers that don't cut the mustard (IE8):
61 |-
62 |
- grunt-svgmin (uses SVGO) to minify and clean up our outputted SVG files 63 |
- grunt-svgstore to create an SVG sprite out of all of the SVG files we save and optimize their final output 64 |
- grunt-svg2png to automatically save PNG versions of all of our SVGs 65 |
- SVG for Everybody to inject our PNG fallbacks for IE8 66 |
This whole workflow relies on Using Grunt (again, or Gulp) to handle these tasks for us, so you'll have to be familiar with using Grunt in order to take advantage of it, and learning Grunt is outside of the scope of this little demo, so if you need help getting started head over to the Grunt.js site and follow the step-by-step guide there.
68 |It's also worth noting that for the sake of really making this workflow quick and easy I'm using the grunt-contrib-watch plugin with the LiveReload option enabled so all I have to run in the command line is grunt
and all of my tasks run when I save a file, and the page refreshes automatically with those changes.
Disclaimer — There are seriously tons of other Grunt plugins that handle the same things I am doing here, and I haven't had to the chance to try them all out, so I'm not saying the is the definitive SVG workflow by any means, it's just one that I've been using lately that has worked really well, but try out others and let me know what's good! The bigger picture here is that it is possible to start using SVGs on your sites today, and working with them can be made easy.
70 |Let's Kill It!
73 |After you have Grunt installed, along with all of the dependencies (if you pulled down the example project to work from you just need to navigate to the assets directory in the command line and run npm install
and it will install all of the Grunt plugins we'll be using), you'll need to set up the Gruntfile.js file to tell Grunt how to work with the plugins we'll be using. Below is my example Gruntfile (I've excluded non-SVG-related tasks that are actually included in the demo files for the sake of the example):
75 | module.exports = function(grunt) {
76 |
77 | // Project configuration.
78 | grunt.initConfig({
79 | pkg: grunt.file.readJSON('package.json'),
80 | svgmin: {
81 | dist: {
82 | files: [{
83 | expand: true,
84 | cwd: 'svgs/',
85 | src: ['*.svg'],
86 | dest: 'svgs/'
87 | }]
88 | },
89 | options: {
90 | plugins: [
91 | { removeViewBox: false }, // don't remove the viewbox attribute from the SVG
92 | { removeEmptyAttrs: false } // don't remove Empty Attributes from the SVG
93 | ]
94 | }
95 | },
96 | svgstore: {
97 | dist: {
98 | files: {
99 | 'svgs/build/svg-defs.svg': ['svgs/*.svg']
100 | },
101 | },
102 | options: {
103 | cleanup: true
104 | }
105 | },
106 | svg2png: {
107 | dist: {
108 | files: [{
109 | flatten: true,
110 | cwd: 'svgs/',
111 | src: ['*.svg'],
112 | dest: '../' }
113 | ]
114 | }
115 | },
116 | watch: {
117 | svgs: {
118 | files: 'svgs/*.svg',
119 | tasks: ['svgmin', 'svgstore', 'svg2png'],
120 | options: {
121 | livereload: true,
122 | },
123 | }
124 | },
125 | });
126 |
127 | require('load-grunt-tasks')(grunt);
128 | grunt.loadNpmTasks('grunt-svgmin');
129 | grunt.loadNpmTasks('grunt-svgstore');
130 | grunt.loadNpmTasks('grunt-svg2png');
131 | grunt.loadNpmTasks('grunt-contrib-watch');
132 |
133 | // Default task(s).
134 | grunt.registerTask('default', ['watch']);
135 |
136 | };
137 |
138 | We call each of the plugins we're using and tell them which files to watch, where to output the generated/revised files, and when to run.
139 |The file structure here is very important to pay attention to, and though it might vary depending on how you like to structure your projects, the basic structure is as follows:
140 |
141 | project-folder/
142 | |
143 | |-- assets # I like to put all of my working stuff in here
144 | | |
145 | | |-- svgs
146 | | |-- icon-killer.svg # This is where you save all your SVGs (saving with the icon- prefix is needed!)
147 | | |-- build/ # Call this folder what you want, it's just to keep the final defs file separate
148 | | |-- svg-defs.svg # The generated defs file we'll include in our document as a sprite map
149 | | |
150 | | |-- Gruntfile.js # Just wanted to note that in my setup, this is where Grunt lives
151 | |
152 | |-- icon-killer.png # The fallback PNG files that are generated are saved in the root
153 | | # (this is not ideal, but works for now)
154 |
155 | Now we actually need some SVGs to save and work with. You can design them yourself or grab some from online somewhere (there are even some pretty great web apps with full icon libraries to choose from that will save as SVG for you like Icomoon and Fontastic). I'm just going to grab Github's vector icon, which they have provided as an Adobe Illustrator file, and we're going to open it up in Illustrator to prepare and save for use on the web.
156 |Illustrator is a giant piece of software and they present you with a ton of options when trying to save your icon as an SVG, so I'm just going to point out a few steps and things to pay attention to that tripped me up for a while.
157 |Fit artboards to artwork bounds — most of the time when you open up a vector file in Ai there will be some substantial blank space around the actual icon, and you'll want to get rid of that before you save it by going to Object > Artboards > Fit to Artwork Bounds (if there are strokes applied to the outside of the icon make sure they don't get ignored — you might have to manually adjust for them).
158 |Make all fills pure black, and all strokes transparent — most of the time we'll be using these SVGs as icons that might be used in various places and we'll want to style them based on the context they are viewed in, so it's likely we'll be changing either their fill or stroke properties in our CSS, but if we save an SVG with a transparent fill or with a fill other than pure black (#000), the SVG file that compiles to our svg-defs.svg file will have an inline fill
attribute that will render it impossible to override via CSS. With strokes you just need to make sure they are transparent and you'll be golden.
Once you've prepared the file properly you can save it by simply going to File > Save As and placing the file into our assets/svgs/ directory. For the sake of making the naming of our fallback PNG images that will be generated after we save, give the filename a prefix of icon-. Make sure you select SVG as the file format (not SVG Compressed), and click Save.
162 |A new window with a bunch of options will pop up and you'll want to make sure of just a couple of things. I honestly don't even really know what the SVG Profiles is about, but the default 1.1 has worked just fine. For the Fonts option you'll almost always want to set it to Convert to outline so that the icon is completely comprised of paths and shapes, and not embedding any fonts. The last bit is to just make sure that the Image Location is set to Embed so that it doesn't generate and save a crappy jpg file on you.
163 |Treehouse has a pretty thorough video tutorial that will help you understand it better if you're still stuck.
165 |Next — In order to include our SVGs with <use>
we need to include our svg-defs.svg file at the top of our body, which we'll hide with display: none;
(I know that feels weird, but it works). For my example I'm going to include it with some PHP:
167 | <span class="hide"><?php include_once("assets/svgs/build/svg-defs.svg"); ?></span>
168 |
169 | There are many reasons I like to work with SVGs this way, but if you want to know more check out this article on CSS-Tricks.
170 |Once we've included the svg-defs.svg file we can now access all of the SVGs we defined there and implement them as if they were inline via the <svg>
and <use>
elements, and a couple of classes to help style them.
Here is what the markup looks like:
172 |
173 | <svg class="icon icon-github" role="img" aria-labelledby="title">
174 | <use xlink:href="#icon-github"></use>
175 | </svg>
176 |
177 | We can then target our individual SVG icons in CSS by the <svg>
element and its class:
179 | .icon-github {
180 | width: 60px;
181 | fill: $brand-main;
182 | }
183 |
184 | And we get this:
185 | 186 |Killer!
189 |Now we can go crazy and use SVGs all over the place, style them uniquely with CSS (In the example project you can mess around with the styles in asstes/sass/components/_svg-icons.scss), animate them, and we don't have to worry a bunch of extra HTTP requests are a bloated icon font file! Plus, with our automatically generated PNG fallbacks for IE8, we can use them pretty confidently and we don't have to even think about it that much!
192 |Examples
195 | 196 | 197 | 198 |The IE8 Fallback
201 |Just thought I should point out what is going on with our PNG fallbacks for IE8.
202 |We just include the svg4everybody.ie8.min.js file in some conditional comments in our header:
203 |
204 | <!--[if lt IE 9]>
205 | <script src="assets/js/no-build/svg4everybody.ie8.min.js"></script>
206 | <![endif]-->
207 |
208 | And then all we're doing is saving PNG versions of our SVG icons (and we're automating that too!), and the script we loaded figures out if the browser is incapable of displaying the SVG icons and replaces each instance with the corresponding PNG file that has the same name as the id we declared as the value of the href attribute for the <use>
element.
So looking back at our Github icon example from above, the generated markup in a browser like IE8 would be:
210 |
211 | <svg class="icon icon-github" role="img" aria-labelledby="title">
212 | <img src="icon-github.png">
213 | </svg>
214 |
215 | Not Quite Killer
218 |This workflow has been great, but it's not perfect. There are still a couple of things I'd like to nail down, and I think they're worth pointing out.
219 |-
220 |
- To really ensure proper semantics you have to go into the svg-defs.svg file and manually enter in a more descriptive title, because by default it just grabs the name of the SVG file you saved (ex: "icon-grunt", which isn't great) 221 |
- Sometimes it's desirable to save an SVG without any fills or strokes applied to give yourself complete control of variation in your CSS, but then the PNG that gets automatically generated is blank, so there are times when it makes sense to go in and manually save a fallback PNG 222 |
- As I pointed out earlier, the SVG For Everybody plugin doesn't currently allow you to specify where to grab the PNG files from, so you're forced to place them in the project root, which is fine because it works, but personally I feel like a bunch of loose PNG files in my root feels cluttered and unorganized, but there is an open issue in the repo addressing this problem, so it could be resolved soon (maybe by you?!) 223 |
Further Reading
227 |There's a lot more to be said about using SVGs and ways to improve the way you implement them, but Chris Coyier already compiled a list of articles that covers just about anything you would want to know related to using SVG:
228 |-
229 |
- A Compendium of SVG Information 230 |