SVG, data URI + SASS
An exploration.

Nov 17, 2013

The concept of writing scalable and modular CSS has widely been acknowledged as a far more efficient way in which to write and manage our CSS. Pre-processing tools such as SASS & LESS provide us with a suite of features enabling us to write reusable and configurable components while frameworks such as Compass, Foundation and Bootstrap provide us powerful features with which to build scalable, cross-platform layouts and stylings.

Wouldn’t it be great if we could do the same for our graphics? What if we could write reusable and configurable scalable vector graphics and serve them all together with one HTTP request?

Enter SVG, Data-URI and SCSS / Compass.

The data URI scheme provides us with a way to embed data/images inline with our HTML/CSS. This introduces a potential performance increase by allowing us to reduce the number of HTTP requests the browser needs to make in order to resolve its dependencies. Compass makes it easy to embed images and fonts with the inline-image and inline-font-files helpers, but where things start to get really interesting are when we embed SVG as a data URI.

While most people are familiar the @mixin and $variable functionality, @functions are often overlooked. Where a @mixin allows you to pre-define a reusable set of properties the @function feature allows you to return a value, which in turn enables us us to do things like this:

SCSS:

@function svgShape($fillColor) {
  @return url('data:image/svg+xml,<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="183.333px" height="50.666px" viewBox="0 125 183.333 50.666" enable-background="new 0 125 183.333 50.666"> <polygon fill="' + $fillColor + '" points="92,125 0,150.333 0,150.666 92,175.666 183.333,150.333 183.333,150 "/> </svg>');
}

.svg-shape {

background-repeat: no-repeat;
  background-size: 100% 100%;
  background-position: 50% 50%;
  
  &.red {
    background-image: svgShape(#ff0000);
  }
  
  &.green {
    background-image: svgShape(#00ff00);	
  }
  
  &.blue {
    background-image: svgShape(#0000ff);
  }
}

HTML:

<div class="svg-shape red">
  <!-- I've got a red SVG background -->
</div>

<div class="svg-shape green">
  <!-- I've got a green SVG background -->
</div>

<div class="svg-shape blue">
  <!-- I've got a blue SVG background -->
</div>

See the Pen Bihcn by Matt Richards (@lucidmoon) on CodePen

Obviously this is a very basic example where we simply change the fill colour but it ought to be enough to demonstrate the power of this technique.

We can pre-configure re-usable SVG elements right inside our stylesheets.

Caveats

A note on cross-browser/platform compatibility

While both SVG and data URI are widely supported, implementations do vary across platforms and the combination of both data URI and SVG can be a bit sketchy across implementaiton. If you need to support IE8 you’re straight out of luck as there is no support for SVG. Other than that the two biggest issues I’ve run into are encoding issues and differing implementations of the CSS3 background-size property.

Encoding

On the encoding front, I’ve had most success using base64 encoded strings. One way to do achieve this would be to encode our SVG before embedding it into our style like so:

background-image: url('data:image/svg,PHN2ZyB2ZXJzaW9uPSIxLjEiIGlkPSJMYXllcl8xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB4PSIwcHgiIHk9IjBweCIgd2lkdGg9IjgzLjVweCIgaGVpZ2h0PSI5NS4yODZweCIgdmlld0JveD0iMCAwIDgzLjUgOTUuMjg2IiBlbmFibGUtYmFja2dyb3VuZD0ibmV3IDAgMCA4My41IDk1LjI4NiIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+PGc+PHBvbHlnb24gZmlsbD0iI0YxNUEyMiIgcG9pbnRzPSIyMy4yNzEsNDUuNDY1IDE1LjQxOCw0OC4xOTMgMjUuMzQzLDY1LjM4MyAyOS4xNzksNjIuNTM4IAkiLz48cG9seWdvbiBmaWxsPSIjRjc5NDFFIiBwb2ludHM9IjE2LjA3OSw0NS44NDYgMjIuNjE2LDQzLjU3NSAyMC42NTgsMzcuOTE2IAkiLz48cG9seWdvbiBmaWxsPSIjRjM3MDIxIiBwb2ludHM9IjIxLjUzNyw3MC42OTcgMjYuMTAyLDcwLjY5NyAyNC43MzMsNjguMzI3IAkiLz48cG9seWdvbiBmaWxsPSIjRjE1QTIyIiBwb2ludHM9IjIxLjExNSwzMy4xMjMgMjYuMTAxLDI0LjQ4NyAxOC4xMjcsMjQuNDg3IAkiLz4JPHBvbHlnb24gZmlsbD0iI0YzNzAyMSIgcG9pbnRzPSIyOC45NzEsNDEuMzY4IDIyLjcyMSwzNy43NiAyNC41MDcsNDIuOTE5IAkiLz4JPHBvbHlnb24gZmlsbD0iI0YzNzAyMSIgcG9pbnRzPSIzMS4yNiw0Mi42OSAyNS4xNiw0NC44MDkgMzAuODYzLDYxLjI4OSA0NS4xMzYsNTAuNzAyIAkiLz4JPHBvbHlnb24gZmlsbD0iI0Y3OTQxRSIgcG9pbnRzPSIzMi4wMDMsNzAuNjk3IDI5Ljg2NSw2NC41MTkgMjYuMzUsNjcuMTI2IDI4LjQxMSw3MC42OTcgCSIvPgk8cG9seWdvbiBmaWxsPSIjRjc5NDFFIiBwb2ludHM9IjEuODA1LDUyLjkyMSA2LjczMyw1OC4wMzQgMTEuNjYxLDQ5LjQ5OCAJIi8+CTxwb2x5Z29uIGZpbGw9IiNGNjg2MUYiIHBvaW50cz0iMTYuMTYxLDcwLjY5NyA3LjEzOCw2MS4zMzQgMS43MzIsNzAuNjk3IAkiLz4JPHBvbHlnb24gZmlsbD0iI0Y3OTQxRSIgcG9pbnRzPSI0OC43NzQsNTAuNDkzIDgzLjUsNzAuNTQyIDgzLjUsMjQuNzMyIAkiLz4JPHBvbHlnb24gZmlsbD0iI0Y3OTQxRSIgcG9pbnRzPSIzLjczMSwyNC40ODcgMTkuMDc2LDMzLjM0NyAxNi4wMSwyNC40ODcgCSIvPgk8cG9seWdvbiBmaWxsPSIjRjc5NDFFIiBwb2ludHM9IjI5LjU2NSwyMi40ODcgNDMuMTE1LDIyLjQ4NyA0MS4wNywyLjU2IAkiLz4JPHBvbHlnb24gZmlsbD0iI0Y3OTQxRSIgcG9pbnRzPSIyMy43MjcsNjYuNTgzIDEzLjkxNyw0OS41OTIgOC4xNzgsNTkuNTMzIDE4LjYyMyw3MC4zNjkgCSIvPgk8cG9seWdvbiBmaWxsPSIjRjc5NDFFIiBwb2ludHM9IjMzLjc1OCw0MS44MjIgNDUuODEzLDQ4Ljc4NCA0NC43MDksMzguMDE5IAkiLz4JPHBvbHlnb24gZmlsbD0iI0YzNzAyMSIgcG9pbnRzPSI3Ny41NzMsMjQuNDg3IDQ1LjMzMSwyNC40ODcgNDYuNDQsMzUuMzAxIAkiLz4JPHBvbHlnb24gZmlsbD0iI0Y2ODYxRiIgcG9pbnRzPSIyMi4yNDEsMzUuMTc0IDMxLjQ2OCw0MC41MDEgNDQuNDk5LDM1Ljk3NSA0My4zMiwyNC40ODcgMjguNDExLDI0LjQ4NyAJIi8+CTxwb2x5Z29uIGZpbGw9IiNGNjg2MUYiIHBvaW50cz0iMTUuMzE3LDIyLjQ4NyAxMy4wNTYsMTUuOTUgMS43MzEsMjIuNDg3IAkiLz4JPHBvbHlnb24gZmlsbD0iI0Y3OTQxRSIgcG9pbnRzPSI1LjY5Miw1OS44MzYgMCw1My45MyAwLDY5LjY5NiAJIi8+CTxwb2x5Z29uIGZpbGw9IiNGNjg2MUYiIHBvaW50cz0iMCwyNS40ODcgMCw1MS40MzEgMTIuNDc3LDQ3LjA5OCAJIi8+CTxwb2x5Z29uIGZpbGw9IiMwMDlGREYiIHBvaW50cz0iMTUuNDg0LDcyLjY5NyAxLjczMSw3Mi42OTcgOS40NjYsNzcuMTYyIAkiLz4JPHBvbHlnb24gZmlsbD0iI0Y3OTQxRSIgcG9pbnRzPSI1MC4yNzcsNzIuNjk3IDUyLjAzOCw4OS44NjIgODEuNzY5LDcyLjY5NyAJIi8+CTxwb2x5Z29uIGZpbGw9IiNGMTVBMjIiIHBvaW50cz0iMTguNDAzLDczLjAyMiAxMS4zNTMsNzguMjUxIDM4LjYxNyw5My45OTMgCSIvPgk8cG9seWdvbiBmaWxsPSIjRjY4NjFGIiBwb2ludHM9IjQ4LjI2Nyw3Mi42OTcgMzQuODEyLDcyLjY5NyA0Mi42MjQsOTUuMjY3IDQyLjY0Myw5NS4yODYgNTAuMTQxLDkwLjk1OCAJIi8+CTxwb2x5Z29uIGZpbGw9IiMwMDlGREYiIHBvaW50cz0iNTAuMDcyLDcwLjY5NyA3OS43NjgsNzAuNjk3IDQ4LjIwMiw1Mi40NzIgCSIvPgk8cG9seWdvbiBmaWxsPSIjMDA5RkRGIiBwb2ludHM9IjE3LjQzNSwyMi40ODcgMjcuMjU2LDIyLjQ4NyA0MC4wMTksMC4zODMgMTQuODE5LDE0LjkzMiAJIi8+CTxwb2x5Z29uIGZpbGw9IiNGMTVBMjIiIHBvaW50cz0iNDYuMTg2LDUyLjQxMyAzMS41NDksNjMuMjcgMzQuMTE5LDcwLjY5NyA0OC4wNjIsNzAuNjk3IAkiLz4JPHBvbHlnb24gZmlsbD0iI0YzNzAyMSIgcG9pbnRzPSIyNy4yNTYsNzIuNjk3IDIwLjg2Nyw3Mi42OTcgMzYuNzk3LDg5LjIyMiAJIi8+CTxwb2x5Z29uIGZpbGw9IiNGNjg2MUYiIHBvaW50cz0iMzIuNjk1LDcyLjY5NyAyOS41NjUsNzIuNjk3IDM3LjM4LDg2LjIzMiAJIi8+CTxwb2x5Z29uIGZpbGw9IiNGMTVBMjIiIHBvaW50cz0iNDUuMTI2LDIyLjQ4NyA4MS43NjksMjIuNDg3IDQyLjgxOSwwIAkiLz4JPHBvbHlnb24gZmlsbD0iI0YzNzAyMSIgcG9pbnRzPSIxMy45MTcsNDUuNTkyIDE5LjUwOSwzNS45MDUgMi43MzIsMjYuMjIgCSIvPgk8cG9seWdvbiBmaWxsPSIjRjE1QTIyIiBwb2ludHM9IjQ2LjY1LDM3LjM0NSA0Ny44MTYsNDguNzEzIDc3LjY2MSwyNi41NzMgCSIvPjwvZz48L3N2Zz4=');

But that’s super ugly and severely limits the usefulness of this technique. A much more sensible approach would be to use Compass’s handy base64Encode function. Sadly Compass doesn’t actually offer this functionality, but we do have the ability to write it ourselves, so that’s what we should do. Create a new file named base-64encode.rb, enter the following and then save it some place safe:

require 'sass'
require 'base64'

module sass::script::functions
    def base64encode(string)
        assert_type string, :string
        sass::script::string.new(base64.encode64(string.value))
    end
    declare :base64encode, :args => [:string]
end

We then need to tell Compass to include the file by simply dropping a require '<PATH_TO_FILE>/base64-encode.rb' in our config.rb. (If you’re using Grunt, then you won’t have a config.rb you require in your compass block in your Gruntfile instead).

Now we can do this instead:

@function svgShape($fillColor) {
  @return url('data:image/svg+xml;base64,' + base64Encode('<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="559px" height="75.373px" viewBox="0 0 559 75.373" enable-background="new 0 0 559 75.373" xml:space="preserve"> <polygon fill="' + $fillColor + '" points="0,75.373 559,75.373 279.5,0 "/> </svg>'));
}

Much better. Some other people have also done some interesting work in this area, particularly with regard to cross platform compatibility. I encourage you to head over to Rod Vagg’s blog to read up on their findings.

preserveAspectRatio + background-size

Another issue I ran into was how different browsers interpret and implement the CSS background-size property and the SVG preserveAspectRatio attribute. The SVG preserveAspectRatio attribute provides us with a set of properties in order for us to control how our SVG behaves upon scaling. Webkit based browsers mostly behave as you would expect them to.

Running the compiled CSS from the following SCSS in a webkit browser does exactly what you hoped it would:

@function svgShape($fillColor, $aspectRatio:"") {
  @return url('data:image/svg+xml;base64,' + base64Encode('<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="183.333px" height="50.666px" viewBox="0 125 183.333 50.666" enable-background="new 0 125 183.333 50.666" preserveAspectRatio="' + $aspectRatio + '"> <polygon fill="' + $fillColor + '" points="92,125 0,150.333 0,150.666 92,175.666 183.333,150.333 183.333,150 "/> </svg>'));

}

.svg-shape {
  background: @include svgShape('#f7941e', 'xMidYMax slice') scroll no-repeat 50% 50%;
  background-size: 100% 100%
}

See the Pen tbdlG by Matt Richards (@lucidmoon) on CodePen

The background fills the entire container, preserving the aspect ratio using a slice from the center of the graphic. Neither Firefox or IE however behave as expected instead preferring the cover value for background-size. Frustrating as it limits granularity of control we have over our background size but the following does actually work, which I figure is just as important.

Unfortunately the only way I’ve been able to patch this difference in interpretation is to sniff the User Agent in a Modernizr Test to add a firefox or ie class to html tag in order to send different styles to Firefox & IE:

JS:

/* I feel dirty */
Modernizr.addTest('firefox', function () {
  return !!navigator.userAgent.match(/firefox/i);
});

Modernizr.addTest('ie', function () {
	return !!navigator.userAgent.match(/MSIE/i);
});

SCSS:

.svg-shape {
  background: @include svgShape('#f7941e', 'xMidYMax slice') scroll no-repeat 50% 50%;
  background-size: 100% 100%;
}

.firefox, 
.ie {
  .svg-shape {
    background-size: cover;
  }
}

Ugly. But it gets ugly out on the front lines some times.

Compiled CSS

One important factor to take into consideration is; until such a time when browsers support pre-processing on the fly we’re stuck with pre-compiling our CSS. So what we may gain in usability from a development perspective we may lose out in terms of file-size and bandwidth usage. Depending on how you use it, this technique could produce an excess of duplicate CSS, so my advice would be to proceed with caution but even with that being said, when used intelligently as part of a wider modular framework it could pay dividends. In reality you may find your pre-compiled CSS actually weighs in less than throwing bitmap images around, however YMMV; and remember just because you can, doesn’t mean you should.

Conclusion

If you don’t need to support legacy browsers and if you’re prepared to do what it takes - warts and all - then this technique could be a realistic option. In my own explorations the real power became apparent as soon as I began working with multiple backgrounds. The ability to construct a layered, scalable, configurable and modular set of widgets really opened up a whole new avenue of exploration. I’ve heard a lot of developers grumble of late that they don’t see the benefit of SASS, or others that write SCSS but complain that they only really use the very basic features available.

I encourage you to explore the SASS/Compass feature set beyond but the basics.

You might just have some fun.