Base64 encoded SVG gradient backgrounds in LESS
So, CSS3 gradients are cool. Sure, you need to use a couple of different vendor prefixes but the syntax is pretty nice
.my-gradient { background: -moz-linear-gradient(top, #00b7ea 0%, #009ec3 100%); /* FF3.6+ */ background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#00b7ea), color-stop(100%,#009ec3)); /* Chrome,Safari4+ */ background: -webkit-linear-gradient(top, #00b7ea 0%,#009ec3 100%); /* Chrome10+,Safari5.1+ */ background: -o-linear-gradient(top, #00b7ea 0%,#009ec3 100%); /* Opera 11.10+ */ background: -ms-linear-gradient(top, #00b7ea 0%,#009ec3 100%); /* IE10+ */ background: linear-gradient(to bottom, #00b7ea 0%,#009ec3 100%); /* W3C */ filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#00b7ea', endColorstr='#009ec3',GradientType=0 ); /* IE6-8 */} |
The only caveat here is the filter declaration for IE. You’re limited to a straight top-to-bottom linear gradient (no multi-stops) but more importantly, the gradient background will overflow any border radius you may have defined.
No problem, IEs 7 and 8 don’t support border-radius anyway but what about IE9? The solution…
SVG background images
The standard SVG gradient background definition looks something like this
<?xml version="1.0" ?> <svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 0 1 1" preserveAspectRatio="none"> <linearGradient id="grad-ucgg-generated" gradientUnits="userSpaceOnUse" x1="0%" y1="0%" x2="0%" y2="100%"> <stop offset="0%" stop-color="#00b7ea" stop-opacity="1"/> <stop offset="100%" stop-color="#009ec3" stop-opacity="1"/> </linearGradient> <rect x="0" y="0" width="1" height="1" fill="url(#grad-ucgg-generated)" /> </svg> |
You can then base64 encode this and set it as a data URI background image which will work beautifully in IE9. For example
background: url(data:image/svg+xml;base64,PD94bWwgdmVyc2l...); |
Doing it with LESS
I’ve been using LESS lately and was almost going to move to SASS + Compass in order to achieve the above but found a way to stick with LESS. Enter the base64DataUriBackground mixin. This can be made very simple if your LESS parsing environment has access to the btoa() function.
.base64DataUriBackground (@encode, @type: ~"image/svg+xml") { @dataUriPrefix: ~"url(data:@{type};base64,"; @dataUriSuffix: ~")"; // with btoa() @b64DataUri: ~`(function(a,b,c){return a+btoa(b)+c})('@{dataUriPrefix}','@{encode}','@{dataUriSuffix}')`; // without @b64DataUri: ~`(function(a,b,c){function e(a){a=a.replace(/\r\n/g,'\n');var b='';for(var c=0;c<a.length;c++){var d=a.charCodeAt(c);if(d<128){b+=String.fromCharCode(d)}else if(d>127&&d<2048){b+=String.fromCharCode(d>>6|192);b+=String.fromCharCode(d&63|128)}else{b+=String.fromCharCode(d>>12|224);b+=String.fromCharCode(d>>6&63|128);b+=String.fromCharCode(d&63|128)}}return b}function f(a){var b='';var c,f,g,h,i,j,l;var m=0;a=e(a);while(m<a.length){c=a.charCodeAt(m++);f=a.charCodeAt(m++);g=a.charCodeAt(m++);h=c>>2;i=(c&3)<<4|f>>4;j=(f&15)<<2|g>>6;l=g&63;if(isNaN(f)){j=l=64}else if(isNaN(g)){l=64}b=b+d.charAt(h)+d.charAt(i)+d.charAt(j)+d.charAt(l)}return b}var d='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';return a+f(b)+c})('@{dataUriPrefix}','@{encode}','@{dataUriSuffix}')`; background: @b64DataUri; } |
Here’s an example gradient mixin
.gradient (@gradient-top, @gradient-bottom) { @svg: ~'<?xml version="1.0" ?><svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 0 1 1" preserveAspectRatio="none"><linearGradient id="grad-ucgg-generated" gradientUnits="userSpaceOnUse" x1="0%" y1="0%" x2="0%" y2="100%"><stop offset="0%" stop-color="@{gradient-top}" stop-opacity="1"/><stop offset="100%" stop-color="@{gradient-bottom}" stop-opacity="1"/></linearGradient><rect x="0" y="0" width="1" height="1" fill="url(#grad-ucgg-generated)" /></svg>'; .base64DataUriBackground(@svg); background: -moz-linear-gradient(top, @gradient-top 0%, @gradient-bottom 100%); /* FF3.6+ */ background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,@gradient-top), color-stop(100%,@gradient-bottom)); /* Chrome,Safari4+ */ background: -webkit-linear-gradient(top, @gradient-top 0%,@gradient-bottom 100%); /* Chrome10+,Safari5.1+ */ background: -o-linear-gradient(top, @gradient-top 0%,@gradient-bottom 100%); /* Opera 11.10+ */ background: -ms-linear-gradient(top, @gradient-top 0%,@gradient-bottom 100%); /* IE10+ */ background: linear-gradient(to bottom, @gradient-top 0%,@gradient-bottom 100%); /* W3C */ filter: ~"progid:DXImageTransform.Microsoft.gradient( startColorstr='@{gradient-top}', endColorstr='@{gradient-bottom}',GradientType=0 )"; /* IE6-8 */ } |
One final thing you will need to do is disable the filter declaration for IE9 (and above). You can achieve this simply through a conditional stylesheet
<!--[if gte IE 9]>
<style type="text/css">.my-gradient-class{filter: none;}</style>
<![endif]--> |
All CSS examples courtesy of the most awesome Ultimate CSS Gradient Generator.


September 16th, 2012 at 4:00 pm
Another simple(r) way to turn off the filter declaration for IE9 is:
:root .my-gradient-class{filter:none}
IE9 introduced support for :root so you won’t hit IE8. Also, in practice, I’ve found making SVG gradients for IE9 cumbersome. You can get most of this flexibility (and much less complexity) by styling an inner box for IE and giving the outer box rounded corners and overflow:hidden. You miss out on multi-stop gradients and overflow for pop-up menus, but it’s flexible enough without adding so many bytes. You might even be able to emulate support for angles by using IE’s rotation transforms, though I haven’t tested this.
That’s another thing. When every syntax is added to a gradient declaration, you end up with a rule that’s almost 2KB long. I hate to be pragmatic but in some cases, an image is actually smaller than this and can save you all the hassle. It’s obviously far less flexible, but is something that shouldn’t be disregarded immediately, especially if the gradient is just a fade from transparent to opaque and might be reusable in some way.