An Animated Tale of SVG Transforms

Tags

Amusement park at night

I needed to animate a <path> within an SVG for a web development project, so I reached for an old demo to recall a method I previously used. But every demo I had created that involved a transformation with a rotation around a percentage-based transform-origin was broken. Upon further investigation, I built the SVGs incorrectly. I was incorrect in my approach to transforming SVG child elements.

In SVG lore transform-origin has historically been a pain when animating using CSS. This post aims to clear up the same misconceptions regarding SVG relative to transform-origin weirdness.

Clearly it’s very broken. Upon further investigation, I was able to notice that the bowtie’s transform-origin was now at the center of the entire SVG, no longer the center of the bowtie’s painted area.

Adding the following one line of CSS to the affected SVG element fixed my transform-origin issue:

transform-box: fill-box;

That’s much better.

transform-origin

Each element has a local coordinate system that transform-origin defaults to. HTML elements have a default transform-origin of 50% 50% of the reference box (the vertical and horizontal center of the element’s border-box). Pending any transforms on the SVG or any of its child elements, SVG elements differ in that their coordinate system’s default transform-origin is (0,0) of its reference box (the top left corner of the SVG’s viewBox).

This issue isn’t present in transform-origin properties that have absolute values (pixels). The following is an example of an absolutely vs relatively defined transform-origin:

transform-origin: 250px 250px; /* absolutely defined value */
transform-origin: 50% 50%; /* relatively defined value */

transform-box

The transform-box property allows us to alter the transform reference box of a particular element. This is similiar behavior to box-sizing when setting padding. It will affect the element differently based on your reference box, border-box or content-box.

The transform-box property has 3 possible values:

  • border-box
  • fill-box
  • view-box
.mySvg {
  animation: rotation 1s linear infinite;
  transform-origin: 50% 50%;
}

@keyframes rotation {
  to {
    transform: rotate(360deg);
  }
}

The default value for the transform reference box of HTML elements is border-box. We can focus on fill-box and view-box since they are specific to SVG elements. fill-box uses the object bounding box as a reference, while view-box used the nearest SVG viewport.

In Chrome until recently, setting the following code on an SVG child element (i.e. <path>) would cause that <path> to transform about the center of the painted area:

.mySvg__path {
  animation: rotation 1s linear infinite;
  transform-origin: 50% 50%;
}

@keyframes rotation {
  to {
    transform: rotate(360deg);
  }
}

alt text

This is the expected behavior of transform-box: fill-box. The issue with this is that the new default value of transform-box is no longer fill-box. In Chrome’s recent update that value has been update to view-box. This is the core of the issue. The center of my transform-origin shifted from the center of my SVG’s child element (<path>) to the nearest viewBox attribute, which resides on the actual SVG. Unless my <path> has the same dimensions as the entire SVG, the expected transform-origin will be incorrect.

alt text

This brings about a fundamental shift in how we handle transforming the entire SVG vs the child elements within those SVGs.

DockYard is a digital product agency offering custom software, mobile, and web application development consulting. We provide exceptional professional services in strategy, user experience, design, and full stack engineering using Ember.js, React.js, Ruby, and Elixir. With staff nationwide, we’ve got consultants in key markets across the U.S., including Portland, Los Angeles, Salt Lake City, Minneapolis, Dallas, Miami, Washington D.C., and Boston.