VickyB
VickyB

Reputation: 190

Responsive SVGs on InternetExplorer with SVG injection

To make SVGs responsive on our website with Internet Explorer 11 I am using the "Canvas Hack" by Nicholas Gallagher. This hack uses an extra canvas element to make use the SVG keeps the aspect ratio. The whole structure of the inline SVGs looks something like this.

HTML:

<div style="position:relative;width:100%;">
  <canvas width="256" height="256"></canvas>
  <svg viewBox="0 0 256 256" preserveAspectRatio="xMaxYMax meet">
    ...
  </svg>
</div>

CSS:

canvas {
    display: block;
    width: 100%;
    visibility: hidden;
}

svg {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
}

I am using SVGInject to make the SVGs inline, which means the SVGs are kept is separate files. Before SVG injection the HTML looks like this:

<div style="position:relative;width:100%;">
  <canvas width="256" height="256"></canvas>
  <img src="myimage.svg" onload="SVGInject(this)" />
</div>

This works well, but maintenance is very annoying, because for each SVG the values of width and height for the canvas must be manually set to match the SVG's aspect ratio. And since the SVGs are kept in separate files, I have to open the SVG every time to find out the aspect ratio.

So I was wondering, is this something that could automatically be done during injection?.

My ideas was to create a script that during injection somehow reads the SVG's aspect ratio from the viewBox attribute and then set the width and height for the canvas accordingly.

Upvotes: 1

Views: 442

Answers (2)

user19074969
user19074969

Reputation: 21

I had to deal with SVGs, created dynamically, so height is not known in advance. I found reference of the canvas hack here: Article about IE11 svg scaling hacks in German. For my solution, I wrote a simple little jQuery script to inject the proper canvas element, which is easy to read, adapt and reuse:

jQuery(function(){
   if( jQuery( "svg.my" ).length > 0 ) {
      var $svg_my = jQuery( "svg.my" );
      for (var i=0; i<$svg_my.length; i++) {
         // ----- viewBox calculation -----
         var $svg_viewBox = jQuery($svg_my[i]).attr('viewBox');
         $svg_viewBox = $svg_viewBox.replace(/\s\s+/g, ' ');
         var $svg_width = $svg_viewBox.split(' ')[2];   // split() creates a string
         var $svg_height = $svg_viewBox.split(' ')[3];
         // ----- HTML - canvas fix -----
         jQuery($svg_my[i]).wrap("<div class='svg_fix'></div>");
         jQuery($svg_my[i]).parent().append("<canvas class='svg_fix' width='" + $svg_width + "' height='" + $svg_height + "'></canvas>");
         jQuery($svg_my[i]).attr('width', '100%').attr('height', '100%');
      }
   }
});
svg.my {
   position: absolute;
   top: 0;
   left: 0;
   width: 100%;
}
div.svg_fix {
   position:relative;
   margin-left:auto; 
   margin-right: auto;
}
canvas.svg_fix {
   display: block;
   width: 100%;
   visibility: hidden;
}

All it does, is, wrapping the svg.my with a div element, and placing inside the div element, side by side with the svg.my, a dummie canvas element, and applying some simple CSS for styling. The canvas element ensures proper zoom and scaling in IE11. Therefore, it needs to be fitted with the width and height of the svg.my, accordingly.

This fix works with multiple svg.my per page. Now, sizing of each svg on the page can easily be achieved by changing the width of the associated canvas element.

Upvotes: 0

Waruyama
Waruyama

Reputation: 3533

SVGInject provides the following hooks to the injection: beforeLoad, afterLoad, beforeInject and afterInject. In your case you can use afterInject to modify the SVG, its parent, siblings, etc..

With using the afterInject hook, you can not only set the width and height attributes of the <canvas> element, but you can even check if Internet Explorer is running and only insert the canvas in that case. This will make your HTML much cleaner.

Here is a script (using jQuery) that will add the canvas only on Internet Explorer. In the <head> add these lines (the one including svg-inject.js should alrady be in your code):

<script src="svg-inject.js"></script>
<script>
  SVGInject.setOptions({afterInject: function(img, svg) {
    if (/Trident|MSIE/.test(window.navigator.userAgent)) { // if Internet Explorer
      var $svg = $(svg);
      // Get width and height from viewBox attribute if available
      var viewBoxVals = ($svg.attr('viewBox') || '').split(/[\s,]+/);
      var width = parseInt(viewBoxVals[2]);
      var height = parseInt(viewBoxVals[3]);
      if (width > 0 && height > 0) {
        // Set position of parent div to relative
        $svg.parent().css({position: 'relative'});
        // Add canvas using width and height from viewBox
        $svg.before('<canvas height="' + height + '" width="' + width + '"' +
         'style="display: block; width: 100%; visibility: hidden;"></canvas>');
        // Set SVG attributes to make it fill space reserved by canvas.
        $svg.css({position: 'absolute', top: 0, left: 0});
      }
    }
  }})
</script>

After the SVG is injected, the script checks if Internet Explorer is running. If so, it extracts the width and height values from the viewBox attribute and inserts a canvas before the SVG. Also, the parent's and the SVG's attributes are set to make the SVG responsive.

SVGs can then be simply added like this:

<div style="width:100%;">
  <img src="myimage.svg" onload="SVGInject(this)" />
</div>

No need to add the canvas in your HTML, it will be automatically inserted on Internet Explorer (and only on Internet Explorer).

Upvotes: 1

Related Questions