361
votes

Placing the SVG output directly inline with the page code I am able to simply modify fill colors with CSS like so:

polygon.mystar {
    fill: blue;
}​

circle.mycircle {
    fill: green;
}

This works great, however I'm looking for a way to modify the "fill" attribute of an SVG when it's being served as a BACKGROUND-IMAGE.

html {      
    background-image: url(../img/bg.svg);
}

How can I change the colors now? Is it even possible?

For reference, here are the contents of my external SVG file:

<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="320px" height="100px" viewBox="0 0 320 100" enable-background="new 0 0 320 100" xml:space="preserve">
<polygon class="mystar" fill="#3CB54A" points="134.973,14.204 143.295,31.066 161.903,33.77 148.438,46.896 151.617,65.43 134.973,56.679 
    118.329,65.43 121.507,46.896 108.042,33.77 126.65,31.066 "/>
<circle class="mycircle" fill="#ED1F24" cx="202.028" cy="58.342" r="12.26"/>
</svg>
17
I get hit up often with props for my answer. You should consider changing it to the accepted answer so it's not missed.Ryan

17 Answers

93
votes

One way to do this is to serve your svg from some server side mechanism. Simply create a resource server side that outputs your svg according to GET parameters, and you serve it on a certain url.

Then you just use that url in your css.

Because as a background img, it isn't part of the DOM and you can't manipulate it. Another possibility would be to use it regularly, embed it in a page in a normal way, but position it absolutely, make it full width & height of a page and then use z-index css property to put it behind all the other DOM elements on a page.

175
votes

I needed something similar and wanted to stick with CSS. Here are LESS and SCSS mixins as well as plain CSS that can help you with this. Unfortunately, it's browser support is a bit lax. See below for details on browser support.

LESS mixin:

.element-color(@color) {
  background-image: url('data:image/svg+xml;utf8,<svg ...><g stroke="@{color}" ... /></g></svg>');
}

LESS usage:

.element-color(#fff);

SCSS mixin:

@mixin element-color($color) {
  background-image: url('data:image/svg+xml;utf8,<svg ...><g stroke="#{$color}" ... /></g></svg>');
}

SCSS usage:

@include element-color(#fff);

CSS:

// color: red
background-image: url('data:image/svg+xml;utf8,<svg ...><g stroke="red" ... /></g></svg>');

Here is more info on embedding the full SVG code into your CSS file. It also mentioned browser compatibility which is a bit too small for this to be a viable option.

163
votes

You can use CSS masks, With the 'mask' property, you create a mask that is applied to an element.

.icon {
    background-color: red;
    -webkit-mask-image: url(icon.svg);
    mask-image: url(icon.svg);
}

For more see this great article: https://codepen.io/noahblon/post/coloring-svgs-in-css-background-images

65
votes

Yet another approach is to use mask. You then change the background color of the masked element. This has the same effect as changing the fill attribute of the svg.

HTML:

<glyph class="star"/>
<glyph class="heart" />
<glyph class="heart" style="background-color: green"/>
<glyph class="heart" style="background-color: blue"/>

CSS:

glyph {
    display: inline-block;
    width:  24px;
    height: 24px;
}

glyph.star {
  -webkit-mask: url(star.svg) no-repeat 100% 100%;
  mask: url(star.svg) no-repeat 100% 100%;
  -webkit-mask-size: cover;
  mask-size: cover;
  background-color: yellow;
}

glyph.heart {
  -webkit-mask: url(heart.svg) no-repeat 100% 100%;
  mask: url(heart.svg) no-repeat 100% 100%;
  -webkit-mask-size: cover;
  mask-size: cover;
  background-color: red;
}

You will find a full tutorial here: http://codepen.io/noahblon/blog/coloring-svgs-in-css-background-images (not my own). It proposes a variety of approaches (not limited to mask).

25
votes

Use the sepia filter along with hue-rotate, brightness, and saturation to create any color we want.

.colorize-pink {
  filter: brightness(0.5) sepia(1) hue-rotate(-70deg) saturate(5);
}

https://css-tricks.com/solved-with-css-colorizing-svg-backgrounds/

21
votes

It's possible with Sass! The only thing you have to do is to url-encode your svg code. And this is possible with a helper function in Sass. I've made a codepen for this. Look at this:

http://codepen.io/philippkuehn/pen/zGEjxB

// choose a color

$icon-color: #F84830;


// functions to urlencode the svg string

@function str-replace($string, $search, $replace: '') {
  $index: str-index($string, $search);
  @if $index {
    @return str-slice($string, 1, $index - 1) + $replace + str-replace(str-slice($string, $index + str-length($search)), $search, $replace);
  }
  @return $string;
}

@function url-encode($string) {
  $map: (
    "%": "%25",
    "<": "%3C",
    ">": "%3E",
    " ": "%20",
    "!": "%21",
    "*": "%2A",
    "'": "%27",
    '"': "%22",
    "(": "%28",
    ")": "%29",
    ";": "%3B",
    ":": "%3A",
    "@": "%40",
    "&": "%26",
    "=": "%3D",
    "+": "%2B",
    "$": "%24",
    ",": "%2C",
    "/": "%2F",
    "?": "%3F",
    "#": "%23",
    "[": "%5B",
    "]": "%5D"
  );
  $new: $string;
  @each $search, $replace in $map {
    $new: str-replace($new, $search, $replace);
  }
  @return $new;
}

@function inline-svg($string) {
  @return url('data:image/svg+xml;utf8,#{url-encode($string)}');
}


// icon styles
// note the fill="' + $icon-color + '"

.icon {
  display: inline-block;
  width: 50px;
  height: 50px;
  background: inline-svg('<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
   viewBox="0 0 30 30" enable-background="new 0 0 30 30" xml:space="preserve">
<path fill="' + $icon-color + '" d="M18.7,10.1c-0.6,0.7-1,1.6-0.9,2.6c0,0.7-0.6,0.8-0.9,0.3c-1.1-2.1-0.4-5.1,0.7-7.2c0.2-0.4,0-0.8-0.5-0.7
  c-5.8,0.8-9,6.4-6.4,12c0.1,0.3-0.2,0.6-0.5,0.5c-0.6-0.3-1.1-0.7-1.6-1.3c-0.2-0.3-0.4-0.5-0.6-0.8c-0.2-0.4-0.7-0.3-0.8,0.3
  c-0.5,2.5,0.3,5.3,2.1,7.1c4.4,4.5,13.9,1.7,13.4-5.1c-0.2-2.9-3.2-4.2-3.3-7.1C19.6,10,19.1,9.6,18.7,10.1z"/>
</svg>');
}
13
votes
 .icon { 
  width: 48px;
  height: 48px;
  display: inline-block;
  background: url(https://s3-us-west-2.amazonaws.com/s.cdpn.io/18515/heart.svg) no-repeat 50% 50%; 
  background-size: cover;
}

.icon-orange { 
  -webkit-filter: hue-rotate(40deg) saturate(0.5) brightness(390%) saturate(4); 
  filter: hue-rotate(40deg) saturate(0.5) brightness(390%) saturate(4); 
}

.icon-yellow {
  -webkit-filter: hue-rotate(70deg) saturate(100);
  filter: hue-rotate(70deg) saturate(100);
}

codeben article and demo

10
votes

Now you can achieve this on the client side like this:

var green = '3CB54A';
var red = 'ED1F24';
var svg = '<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="320px" height="100px" viewBox="0 0 320 100" enable-background="new 0 0 320 100" xml:space="preserve"> <polygon class="mystar" fill="#'+green+'" points="134.973,14.204 143.295,31.066 161.903,33.77 148.438,46.896 151.617,65.43 134.973,56.679 118.329,65.43 121.507,46.896 108.042,33.77 126.65,31.066 "/><circle class="mycircle" fill="#'+red+'" cx="202.028" cy="58.342" r="12.26"/></svg>';      
var encoded = window.btoa(svg);
document.body.style.background = "url(data:image/svg+xml;base64,"+encoded+")";

Fiddle here!

9
votes

Download your svg as text.

Modify your svg text using javascript to change the paint/stroke/fill color[s].

Then embed the modified svg string inline into your css as described here.

8
votes

You can store the SVG in a variable. Then manipulate the SVG string depending on your needs (i.e., set width, height, color, etc). Then use the result to set the background, e.g.

$circle-icon-svg: '<svg xmlns="http://www.w3.org/2000/svg"><circle cx="10" cy="10" r="10" /></svg>';

$icon-color: #f00;
$icon-color-hover: #00f;

@function str-replace($string, $search, $replace: '') {
    $index: str-index($string, $search);

    @if $index {
        @return str-slice($string, 1, $index - 1) + $replace + str-replace(str-slice($string, $index + str-length($search)), $search, $replace);
    }

    @return $string;
}

@function svg-fill ($svg, $color) {
  @return str-replace($svg, '<svg', '<svg fill="#{$color}"');
}

@function svg-size ($svg, $width, $height) {
  $svg: str-replace($svg, '<svg', '<svg width="#{$width}"');
  $svg: str-replace($svg, '<svg', '<svg height="#{$height}"');

  @return $svg;
}

.icon {
  $icon-svg: svg-size($circle-icon-svg, 20, 20);

  width: 20px; height: 20px; background: url('data:image/svg+xml;utf8,#{svg-fill($icon-svg, $icon-color)}');

  &:hover {
    background: url('data:image/svg+xml;utf8,#{svg-fill($icon-svg, $icon-color-hover)}');
  }
}

I have made a demo too, http://sassmeister.com/gist/4cf0265c5d0143a9e734.

This code makes a few assumptions about the SVG, e.g. that <svg /> element does not have an existing fill colour and that neither width or height properties are set. Since the input is hardcoded in the SCSS document, it is quite easy to enforce these constraints.

Do not worry about the code duplication. compression makes the difference negligible.

4
votes

You can create your own SCSS function for this. Adding the following to your config.rb file.

require 'sass'
require 'cgi'

module Sass::Script::Functions

  def inline_svg_image(path, fill)
    real_path = File.join(Compass.configuration.images_path, path.value)
    svg = data(real_path)
    svg.gsub! '{color}', fill.value
    encoded_svg = CGI::escape(svg).gsub('+', '%20')
    data_url = "url('data:image/svg+xml;charset=utf-8," + encoded_svg + "')"
    Sass::Script::String.new(data_url)
  end

private

  def data(real_path)
    if File.readable?(real_path)
      File.open(real_path, "rb") {|io| io.read}
    else
      raise Compass::Error, "File not found or cannot be read: #{real_path}"
    end
  end

end

Then you can use it in your CSS:

.icon {
  background-image: inline-svg-image('icons/icon.svg', '#555');
}

You will need to edit your SVG files and replace any fill attributes in the markup with fill="{color}"

The icon path is always relative to your images_dir parameter in the same config.rb file.

Similar to some of the other solutions, but this is pretty clean and keeps your SCSS files tidy!

2
votes

In some (very specific) situations this might be achieved by using a filter. For example, you can change a blue SVG image to purple by rotating the hue 45 degrees using filter: hue-rotate(45deg);. Browser support is minimal but it's still an interesting technique.

Demo

2
votes

for monochrome background you could use a svg with a mask, where the background color should be displayed

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" preserveAspectRatio="xMidYMid meet" focusable="false" style="pointer-events: none; display: block; width: 100%; height: 100%;" >
    <defs>
        <mask id="Mask">
            <rect width="100%" height="100%" fill="#fff" />
            <polyline stroke-width="2.5" stroke="black" stroke-linecap="square" fill="none" transform="translate(10.373882, 8.762969) rotate(-315.000000) translate(-10.373882, -8.762969) " points="7.99893906 13.9878427 12.7488243 13.9878427 12.7488243 3.53809523"></polyline>
        </mask>
    </defs>
    <rect x="0" y="0" width="20" height="20" fill="white" mask="url(#Mask)" />
</svg>

and than use this css

background-repeat: no-repeat;
background-position: center center;
background-size: contain;
background-image: url(your/path/to.svg);
background-color: var(--color);
1
votes

Late to the show here, BUT, I was able to add a fill color to the SVG polygon, if you're able to directly edit the SVG code, so for example the following svg renders red, instead of default black. I have not tested outside of Chrome though:

<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="500px" height="500px" viewBox="0 0 500 500" enable-background="new 0 0 500 500" xml:space="preserve">
    <polygon 


        fill="red"


        fill-rule="evenodd" clip-rule="evenodd" points="452.5,233.85 452.5,264.55 110.15,264.2 250.05,390.3 229.3,413.35 
47.5,250.7 229.3,86.7 250.05,109.75 112.5,233.5 "/>
</svg>
0
votes

You can use the brightness filter, any value greater than 1 makes the element brighter, and any value less than 1 makes it darker. So, we can make those light SVG’s dark, and vice versa, for example, this will make the svg darker:

filter: brightness(0);

In order to change the color and not only brightness level we can use sepia filter along with hue-rotate, brightness, for example:

.colorize-blue {
  filter: brightness(0.5) sepia(1) hue-rotate(140deg) saturate(6);
}
-2
votes

This is my favorite method, but your browser support must be very progressive. With the mask property you create a mask that is applied to an element. Everywhere the mask is opaque, or solid, the underlying image shows through. Where it’s transparent, the underlying image is masked out, or hidden. The syntax for a CSS mask-image is similar to background-image.look at the codepenmask

-5
votes

A lot of IFs, but if your pre base64 encoded SVG starts:

<svg fill="#000000

Then the base64 encoded string will start:

PHN2ZyBmaWxsPSIjMDAwMDAw

if the pre-encoded string starts:

<svg fill="#bfa76e

then this encodes to:

PHN2ZyBmaWxsPSIjYmZhNzZl

Both encoded strings start the same:

PHN2ZyBmaWxsPSIj

The quirk of base64 encoding is every 3 input characters become 4 output characters. With the SVG starting like this then the 6-character hex fill color starts exactly on an encoding block 'boundary'. Therefore you can easily do a cross-browser JS replace:

output = input.replace(/MDAwMDAw/, "YmZhNzZl");

But tnt-rox answer above is the way to go moving forward.