SVG can use percentage as unit, but it is often more convenient to use px as unit. Framework such as d3 also uses px internally. However, it is still easy to scale an SVG using attributes viewBox and preserveAspectRatio even px is used as unit internally.

viewBox and preserveAspectRatio

The attribute viewBox specifies viewpoint dimension. SVG can be scaled along with the parent container without changing the internal coordinates.

For example, the SVG below defines a viewpoint with width 100 and height 100.

<svg viewBox="0 0 100 100">
  <circle cx="50" cy="50" r="50" fill="blue">
</svg>

The figure below shows the same SVG in different container sizes: 50x50, 100x50, 100x100, 100x200, 200x200 from left to right.

Same SVG in Different Size Containers
Same SVG in Different Size Containers

Attribute preserveAspectRatio controls how SVG is scaled according to parent container. There are many combinations, but the most frequently used are default (when the attribute is not specified, or set to xMidYMid meet explicitly) and none.

  • default, preserve ratio, align center of SVG to the center of the container, scaled up as much as possible but ensure the entire viewBox is visible within the container.
  • none, do not preserve ratio, scale SVG to fill the whole container.

See the figure similar to the previous one but setting preserveAspectRatio to none.

`preserveAspectRatio="none"`
preserveAspectRatio="none"

Responsive SVG

First add following CSS:

svg {
  width: 100%;
  height: 100%;
}

svg * {
  vector-effect: non-scaling-stroke;
}

The first rule ensures SVG always fills parent container. And the second one sets a default vector-effect that when SVG is scaled, leave stroke width untouched.

If the SVG does not need to preserve ratio, set preserveAspectRatio to none, and set the height of the container. For example, the SVG below has a fixed height 50px, and is half width of the whole page.

<div style="width:50%;height:50px;">
  <svg viewBox="0 0 100 100" preserveAspectRatio="none">
    <circle cx="50" cy="50" r="50" fill="blue">
  </svg>
</div>
Fixed With, Horizontal scalable

If the SVG must preserve ratio, then we need a bit of JavaScript to change container height when window is resized.

$.fn.preserveAspectRatio = function() {
  this.each(function() {
    var $this = $(this);
    var ratioSpec = $this.attr('data-preserveAspectRatio').split(':');
    var ratio = parseInt(ratioSpec[1], 10) / parseInt(ratioSpec[0], 10);
    $this.height($this.width() * ratio);
  });
};

var preserveAspectRatio = function() {
  $('[data-preserveAspectRatio]').preserveAspectRatio();
};

$(function() {
  preserveAspectRatio();
  $(window).on('resize', preserveAspectRatio);
  // Better to wrap preserveAspectRatio in underscore debounce or jquery-throttle-debounce.
  // $(window).on('resize', _.debounce(preserveAspectRatio, 200));
});

Then set data-preserveAspectRatio on any container that needs preserve aspect ratio. Following example sets the width height ratio to 1:1:

<div style="width:50%;" data-preserveAspectRatio="1:1">
  <svg viewBox="0 0 100 100" preserveAspectRatio="none">
    <circle cx="50" cy="50" r="50" fill="blue">
  </svg>
</div>

See the demo in this pen.

References