25
votes

I'm trying to construct inline SVGs with viewBox attributes, but no explicit width or height attributes. I'm setting the SVG's width to 100% using CSS. This should let the SVG scale to its wrapping container, maintaining the aspect ratio set up by the viewBox. In Chrome and Firefox, this works perfectly!

Here's a minimal codepen example of what I'm talking about: http://codepen.io/pcorey/pen/amkGl

HTML:

<div>
  <svg viewBox="0 0 100 10">
    <text x="1" y="9">hello</text>
  </svg>
</div>

CSS:

div {
  width: 400px;
}

svg {
  width: 100%;
  max-height: 100%;
  outline: 1px solid tomato;
}

text {
  font-size: 10px;
}

The viewBox is 100x10. The outer div is set to have a 400px width. That means that the SVG's height should be (and is in Chrome/Firefox) 40px. BUT, in IE 11, the width is ALWAYS 150px (even when the div's width exceeds 1500px...)

Is there a nice way to fix this in IE? Why can't IE handle the unknown height correctly? I can use the "intrinsic aspect ratio" trick, but that is super ugly, requires another DOM element, and requires that I recompute the padding-top every time the wrapper resizes.

For more info on why I want to do this, I wrote a quick blog post about it: http://1pxsolidtomato.com/2014/10/08/quest-for-scalable-svg-text/

Thanks for the help!

2
See section SVG embedded inline using the <svg> tag tympanus.net/codrops/2014/08/19/making-svgs-responsive-with-cssAna
Ah, yeah that will work. I tried doing that, but I was using calculating a pixel padding. If I use a percentage instead it works perfectly. Thanks!pcorey
This article didn't exist when you asked your question. It covers the topic in depth: How to Scale SVGMentalist

2 Answers

4
votes

One workaround that should work in all browsers is to add a blank image to the container your SVG sits inside, which has the same dimensions as your SVG:

.wrap {
  position: relative;
}
img {
  width: 100%;
  height: auto;
}
.viz {
  position: absolute;
  top: 0;
  left: 0;
}
<div class="wrap">
  <img src="img/blank.png" />
  <div class="viz">
    <svg preserveAspectRatio="xMinYMin slice" viewBox="0 0 1000 600"></svg>               
  </div>
</div>

In this case, your image should have natural dimensions of 1000px by 600px and be transparent (or matching the background of your .wrap container). This will appropriate the size of the container the svg sits inside. The absolute position of the .viz element will allow it to sit on top of the image, utilizing it's height so nothing gets cut off.

3
votes

Some browsers(IE and safari) will be use a default size to SVG if you don't put a certain height and width. That what is happening here. You are right, the "intrinsic aspect ration" is requiring another Dom and css and will be nice if we can overcame this.

There is a solution to this, you can calculate and put the right height to padding-bottom and this will give the right "unknown height" you want. You can see a full solution here: http://codepen.io/tomkarachristos/pen/GZPbgZ

<!--
xMidYMin: Align the midpoint X value of the element's viewBox with the midpoint X value of the viewport.
slice : the entire viewport is covered by the viewBox and the viewBox is scaled down as much as possible,
height: if you dont set >= 1px some browsers will not render anything.
-->
<div>
    <svg viewBox="0 0 100 10" preserveAspectRatio="xMidYMin slice"
         width="100%" style="height: 1px;overflow: visible;
         /*without js:padding-bottom:55%*/"><text>hello</text>
  </svg>
    <svg viewBox="0 0 100 10" preserveAspectRatio="xMidYMin slice"
         width="100%" style="height: 1px;overflow: visible;"><text>Age</text>
  </svg>
</div>

and javascript:

/*
Here we do the hack.
With the math type: percent * height/width
we are adjust the total height of an element who is depend in width using the padding-bottom.
You can put an inline padding-bottom if you want in html.
*/

$(function() {
  $('svg').each(function() {
    var svg = $(this);
    var text = svg.find('text');
    var bbox = text.get(0).getBBox();
    //the hack
    var calcString = "calc(100% * " + bbox.height/bbox.width + ")";
    svg.css("padding-bottom",calcString);

    svg.get(0).setAttribute('viewBox',
                           [bbox.x,
                            bbox.y,
                            bbox.width,
                            bbox.height].join(' '));
  });
});