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);
}
}
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.
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.