Modifying SVG background fills

September 29, 2014

Working with generated SVG-sprites provide access to individual symbols like so:

<svg class="icon">
  <use xlink:href="sprite.svg#list"></use>
</svg>

Since the sprites are added statically within the document; we get the benefit of saving that data in cache. Using fill: currentColor, we're also able to inherit any font-colors attached to the element; which automatically updates the SVG fill color as well - awesome!

As with most things I dive into with too much optimism - I ran into a snag. Inline SVGs work great, but when specifying a specific SVG as a background, I ran into a couple of issues:

  1. You can't specify xlink:href attributes within CSS; so I'd need to reference an individual SVG - no biggy.
  2. You can't modify the fill color the same as you would an icon font (with a simple color attribute).

Aside from the first; which I could deal with, not being able to modify the fill color of a SVG specified as a background-image seemed like a deal-breaker to me. Here's a few solutions to the issue.

Less

/**
 * @src          The path to your SVG file
 * @fill-default The default fill value of your referenced SVG
 * @fill-new     The fill value you would like to replace it with
 */
.icon-replace-fill(@src, @fill-default, @fill-new) {
  @escape-fill-default: escape(@fill-default);
  @escape-fill-new: escape(@fill-new);
  @data-uri: data-uri('image/svg+xml;charset=UTF-8', '@{src}');
  @replace-src: replace(
    '@{data-uri}',
    '@{escape-fill-default}',
    '@{escape-fill-new}'
  );
  background-image: e(@replace-src);
}

Within the function, we are:

  1. Escaping both the @fill-default and @fill-new values (you can write them as normal HEX values this way in your mixin call)
  2. Using the data-uri() function to grab the data-uri version of your SVG; also setting the MIME type appopriately.
  3. Using the replace() function to substitute the default fill-color with your new color.
  4. Escaping the final data-uri and returning a background-image property with that value set.
Original
With Mixin

New fills

Depending on "how" you're using SVGs, you may be working with paths that do not have fills in them. This would come into play when you're referencing an external sprite, but still want to be able to use things like fill: currentColor within your styles. Here's a mixin that handles just that:

/**
 * @src      The path to your SVG file
 * @fill-new The fill value you would like to have injected into your paths
 */
.icon-add-fill(@src, @fill-new) {
  @data-uri: data-uri('image/svg+xml;charset=UTF-8', '@{src}');
  @replace-default: escape('<path ');
  @replace-new: escape('<path fill="@{fill-new}" ');
  @replace-src: replace('@{data-uri}', @replace-default, @replace-new, 'g');
  background-image: e(@replace-src);
}

Within the function, we are:

  1. Using the data-uri() function to grab the data-uri version of your SVG; also setting the MIME type appopriately.
  2. Using the replace() function to substitute <path with <path fill="YOUR FILL COLOR" with the optional g regex flag, which matches all cases.
  3. Escaping the final data-uri and returning a background-image property with that value set.
Original
With Mixin

Replacing multiple fills

This one involves an SVG that has multiple different fills that you want to be replaced by a single value:

/**
 * @src  The path to your SVG file
 * @fill The fill value you would like to have injected into each path.
 */
.icon-fill(@src, @fill) {
  @data-uri: data-uri('image/svg+xml;charset=UTF-8', '@{src}');
  @replace-src: replace(
    '@{data-uri}',
    'fill\%3D\%22\%23[\w]{3,6}\%22',
    escape('fill="@{fill}"'),
    'g'
  );
  background-image: e(@replace-src);
}

Within the function, we are:

  1. Using the data-uri() function to grab the data-uri version of your SVG; also setting the MIME type appopriately.
  2. Using the replace() function to substitute (the escaped version of) fill="#HEX" with fill="#YOUR HEX" with the optional g regex flag, which matches all cases.
  3. Escaping the final data-uri and returning a background-image property with that value set.
Original
With Mixin

PostCSS

Don't worry, I didn't forget about PostCSS. Well on it's way to being many developer's build-tool of choice with it's vast array of plugins, I personally use it in conjunction with Sass. So how would we go about taking advantage of modifying inline SVGs with it? The postcss-inline-svg package does this for us in a more "native-looking" syntax that's quite easy to bundle alongside your build-tool of choice.

Once installed, loading an SVG in-memory and performing manipulations to internal paths/shapes is possible.

// before
@svg-load nav url(img/nav.svg) {
  fill: #cfc;
  path:nth-child(2) {
    fill: #ff0;
  }
}
.nav {
  background: svg-inline(nav);
}

// after
.nav {
  background: url("data:image/svg+xml;charset=utf-8,%3Csvg fill='%23cfc'%3E%3Cpath d='...'/%3E%3Cpath d='...' fill='%23ff0'/%3E%3Cpath d='...'/%3E%3C/svg%3E");
}