Changing the colour of SVG Images using CSS & jQuery

This is a problem that I solved once I had to work on some crazy enterprise level application and I had no access to the media repository, where I had to change a few colours of the SVG logo. Having given a restricted access, there's no possibility that I can go and edit the SVG file, where the colours are defined. My only hope is to change the colours from the HTML page where the contents are rendered.

We are trying to solve this problem here:

Currently, there isn't an easy way to embed an SVG image and then have access to the SVG elements via CSS. There are various methods of using JS SVG frameworks, but they are overly complicated if all you are doing is making a simple icon with a rollover state.

I thought of creating something easy and generic for others too to make it better. The best way I could think of is to get the contents of the SVG and put it inline in the document so that it's accessible inside the body for CSS or JavaScript to manipulate it. So technically I am just replacing the <img /> tag into <svg /> tag. That's all.

Steps are really simple. First, let's include the logo as <img /> into our page that refers the SVG file.

<img id="logo" class="svg logo" src="logo.svg"/>  

This is same as how you include an image in the HTML. This will work out for almost all the images that are supported by the browser. A decent list includes:

  • Joint Photographic Experts Group (.jpg.jpeg.jpe .jif.jfif.jfi)
  • Graphics Interchange Format (.gif)
  • Portable Network Graphics (.png)
  • Scalable Vector Graphics (.svg)
  • WebP (.webp)
  • Bitmap image file or device independent bitmap (.bmp, .dib) - Never ever use this!

So, here I am now dealing with <svg /> images. In the above code, I have added a class svg to the image for an easy way of selecting it. This way, I could just loop through all the images of this class and try to replace them in one single go. See the below script for looping through all the SVG images of this class individually.

$("img.svg").each(function () {

});

I have used the image tag in the selector too because I don't want to mess up with the <div> tags with svg class. Before any manipulation, we need to take care of the id and class names that tag contains. So, let's cache them first as soon as we cache the current image as jQuery object.

// For each image with an SVG class, execute the following function.
$("img.svg").each(function () {
  // Perf tip: Cache the image as jQuery object so that we don't use the selector muliple times.
  var $img = $(this);
  // Get the ID and classes.
  var imgID = $img.attr('id');
  var imgClass = $img.attr('class');
});

Let's fire an AJAX call to the SVG file and get the contents. Remember, the raw data in SVG file will always come with the XML Document Type Definition or meta data, which we need to remove.

// For each image with an SVG class, execute the following function.
$("img.svg").each(function () {
  // Perf tip: Cache the image as jQuery object so that we don't use the selector muliple times.
  var $img = $(this);
  // Get the ID and classes.
  var imgID = $img.attr("id");
  var imgClass = $img.attr("class");
  // Get the image's URL.
  var imgURL = $img.attr("src");
  // Fire an AJAX GET request to the URL.
  $.get(imgURL, function (data) {
    // The data you get includes the document type definition, which we don't need.
    // We are only interested in the <svg> tag inside that.
    var $svg = $(data).find('svg');
  });
});

The $svg variable will now have the <svg /> tag from the SVG image as a jQuery object. Now we need to do a few more things on this image object.

  1. Add the original attributes of the image tag.
  2. Remove any invalid XML tags, as given by W3C Validator.
  3. Replace the <img /> tag with <svg /> tag as an inline SVG image.

We cached a few details about the image tag using the following code:

var $img = $(this);  
// Get the ID and classes.
var imgID = $img.attr("id");  
var imgClass = $img.attr("class");  

What if there are more attributes? Now with the rising use of HTML5 data-* attributes, it's always better we have to consider the other options and opportunities to include other potential tags, since we are doing this for a generic purpose. So, let's change the above code to something that takes in account, all the attributes and applies:

var $img = jQuery(this);  
// Get all the attributes.
var attributes = $img.prop("attributes");  

And then use a $.each() jQuery function (not to be confused with the .each()) to apply them.

$.each(attributes, function() {
  $svg.attr(this.name, this.value);
});

Finally, we replace the original image with the jQuery $.replaceWith() function.

// Replace image with new SVG
$img.replaceWith($svg);

To put everything in place, we get this code:

// For each image with an SVG class, execute the following function.
$("img.svg").each(function () {
  // Perf tip: Cache the image as jQuery object so that we don't use the selector muliple times.
  var $img = jQuery(this);
  // Get all the attributes.
  var attributes = $img.prop("attributes");
  // Get the image's URL.
  var imgURL = $img.attr("src");
  // Fire an AJAX GET request to the URL.
  $.get(imgURL, function (data) {
    // The data you get includes the document type definition, which we don't need.
    // We are only interested in the <svg> tag inside that.
    var $svg = $(data).find('svg');
    // Remove any invalid XML tags as per http://validator.w3.org
    $svg = $svg.removeAttr('xmlns:a');
    // Loop through original image's attributes and apply on SVG
    $.each(attributes, function() {
      $svg.attr(this.name, this.value);
    });
    // Replace image with new SVG
    $img.replaceWith($svg);
  });
});

The massive advantage about this technique is that it allows you to use CSS to change the properties of the SVG and also manipulate the SVG using jQuery. Let's try to change the fill and it should work seamlessly here:

svg:hover path {  
  cursor: pointer;
  fill: red;
}

Since the above code ports all the attributes too, it works with ID and Class selectors too like this:

#logo:hover path {
  cursor: pointer;
  fill: red;
}

Or like using classes:

.logo:hover path {
  cursor: pointer;
  fill: red;
}

See a working demo of it below:

See the Pen Changing SVG properties using jQuery & CSS by Praveen Kumar (@praveenscience) on CodePen.



comments powered by Disqus