3 Ways to Use Independent Transform Properties

I figure there are three main ways we can break out the individual transform functions (such as translate and scale) from the transform property without a library. The ability to set a new rotate without it affecting a previously set scale, for example, has long been desired by web developers. We’ll take a look at the pros and cons of each approach.

These pros and cons will be centered around each approach’s ability to support the following:

  1. Can we do any number of functions (multiple rotates, multiple scales, etc.)?
  2. Can we specify the order (e.g. scale then translate gives a different end result than translate then scale)?
  3. Can they be transitioned (via transition)?
  4. Can they be animated (via CSS Animations/keyframes or the Web Animations API)?

To see why the first two points matter, play around with the following pen by reordering the transform functions and adding more.

See the Pen Visual Reference: Transform Coordinate Systems by Dan Wilson (@danwilson) on CodePen.

#1 Actual Independent Properties

Defined in the in-progress CSS Transforms Level 2 specification (and in Chrome behind the Experimental Web Platform Features flag), translate, scale, and rotate become their own properties. They can be handled individually, and therefore each can be animated and transitioned separately, too.

.independent-properties {
  translate: 40px 10vmin; /* X Y */
  rotate: 45deg; 
  scale: .9 .9; /* specify both X and Y */
}
.one-transform {
  transform: translate(40px, 10vmin) rotate(45deg) scale(.9); 
}

The two rulesets have equivalent transformations. Now whenever we want to modify the transform — via classes or directly in JavaScript, for example — we can change only the desired property:

.independent-properties:hover {
  rotate: 225deg;
}
.one-transform:hover {
  transform: translate(40px, 10vmin) rotate(225deg) scale(.9); 
}

You can transition and animate each individually as well, so for example you can specify different easings and durations for your translate than your rotate.

Pros

  • Straightforward syntax
  • Can be transitioned and animated independently

Cons

  • Skew function is not represented
  • Can only do one function per axis for each property… if you need to do multiple transformations you must use the transform property
  • Always applied in the order: translate, rotate, scale… if you need a different order you must go back to the transform property
  • Only in Blink browsers with flag enabled as of this writing.

To be fair, the order that these individual properties are applied is a very common order for people to use, and multiple transformations are rarer, too. They still are a key difference with the transform property which gives a lot more power.

#2 Custom Properties

I discovered the possibilities of mixing custom properties with the transform property earlier this year, and well… I’m still excited about them. I’ll summarize the process here, but please check out the original article for more details.

The idea here is that we set up the standard transform property as we always have with every transformation we want to eventually apply using custom properties (which are supported in Firefox, Edge, Safari, Chrome, and Opera).

.using-custom-properties {
  --translate-x: 40px;
  --translate-y: 10vmin;
  --rotate: 45deg; 
  --scale: .9;

  transform:
    translateX(var(--translate-x, 0))
    translateY(var(--translate-y, 0))
    rotate(var(--rotate, 0deg))
    scale(var(--scale, 1))
    translateX(var(--end-translate-x, 0))
}
.using-custom-properties.changed {
  --end-translate-x: 5px;
}

Now we can specify the order of our transformations, and we can do as many transformations as we want, since we are using our old friend transform.

We can even do transitions on the transform and any changes to one of the custom properties will trigger the transition. So in our example we could set a transition: transform 200ms ease-in-out in our rule and when the changed class is added we will smoothly move to our new location. The caveat here is that it is specifically a transition on the single transform property so your translation, for example, cannot have a different duration, easing, or delay than your scale.

Here is an example showing this approach on a button with multiple states. It also shows the first approach if you are in an enabled browser so you can compare them.

See the Pen Button Example: CSS Dev Conf by Dan Wilson (@danwilson) on CodePen.

Pros

  • Any number of transforms allowed
  • We can use any order we want
  • Transitions
  • Supported anywhere custom properties are (Apple, Microsoft, Mozilla, Google, Opera all have them)

Cons

  • More verbose
  • Need to account for all potential transformations up front
  • Keyframe animations and Web Animations API won’t work as intended

About that last bullet point…

As I discussed in an article about animating Single Div art, the Custom Properties Level 1 specification does not introduce mechanisms to specify a syntax or value type for a given custom property. Therefore, the following will either do nothing or flip states at the 50% mark in the animation:

.using-custom-properties {
  /* using set up in last example */
  animation: move-it 1000ms infinite alternate;
}
@keyframes move-it {
  25% {
    --translate-y: 20vmin;
  }
  50% {
    --scale: 1;
  }
  100% {
    --scale: .8;
    --rotate: 90deg;
  }
}

Houdini + Custom Properties

Except that animation can in fact work… in the future! And you can sneak into the future if you have the Experimental Web Platform Features flag enabled in Chrome as it has some initial Houdini magic in it. If you are new to Houdini (and almost all of us are) it’s central idea is that developers can write some JavaScript to tell the browser how to render CSS it doesn’t know what to do with by default. I highly recommend enabling the aforementioned flag in Chrome (under chrome://flags) and visiting this Houdini playground (and its references) to get an idea of how it works and what is possible.

One of the simpler things that Houdini provides is a way to tell the browser that a specific custom property follows a certain syntax. Now we can tell the browser via JavaScript to treat our --translate-x custom property as a length value and our --rotate as an angle. With that small amount of extra information, the browser now has what it needs to do the keyframe animation as we would want.

See the Pen Houdini: Independent Transforms in Keyframes by Dan Wilson (@danwilson) on CodePen.

#3 Composite/Add Animations

Now that you have a taste for the future and are okay with living in dev browsers and/or enabled flags, I want to talk about an old favorite of mine: the Web Animations API. This approach is not like the others and has a smaller use case, but it is still a way to work with transform functions independently.

There are times you have multiple animations going on for the same element — maybe one is changing the opacity, while the other is moving it across the screen. They can have different durations, easings, etc. In the Web Animations API you can achieve this with the following:

element.animate({
  transform: ['translateX(0)', 'translateX(20px)']
}, 1500);
element.animate({
  opacity: [0,1]
}, 1000);

Let’s change that to be two animations with two different transform setups.

element.animate({
  transform: ['translateX(0)', 'translateX(20px)']
}, 1000);
element.animate({
  transform: ['rotate(0)', 'rotate(90deg)']
}, 1500);

Since those are created and running at the same time, only the rotation is applied because they are modifying the same property and the first animation’s requested changes are effectively ignored.

But!

The Web Animations specification introduces the composite operation (and the related iterationComposite). The default composite is replace and has the behavior we have had for years now where an animated property’s value simply replaces any previously set value — either from a rule set or another animation.

The add value is where things change from the previous norms. Taking our example above with the two transform animations, we can modify the second to use the new composite value of add.

element.animate({
  transform: ['translateX(0)', 'translateX(20px)']
}, {
  duration: 1000,
  fill: 'both'
});
element.animate({
  transform: ['rotate(0)', 'rotate(90deg)']
}, {
  duration: 1500,
  fill: 'both',
  composite: 'add'
});

Now both animations will be seen as the browser on the fly figures out the appropriate transformation at a given point in the animation’s timeline accounting for both transformations. In our examples, the easing is 'linear' by default, so we can break out what the effective transform is at any given point. Such as:

  • 0ms: translateX(0px) rotate(0deg)
  • 500ms: translate(10px) rotate(30deg) (halfway through first animation, 1/3 through second)
  • 1000ms: translate(20px) rotate(60deg) (end of first, 2/3 through second)
  • 1500ms: translate(20px) rotate(90deg) (end of second)

The Web Animations API is in Firefox, Chrome, and Opera already. However, this piece of functionality is currently only in Firefox Nightly. You can see it in action in that browser (Nightly is v58 as of this writing) in the following samples. If you view them in another browser you will see the typical replace behavior instead.

See the Pen Animation Composite Add (Firefox Nightly) by Dan Wilson (@danwilson) on CodePen.

See the Pen Animation Composite Tests (WAAPI) by Dan Wilson (@danwilson) on CodePen.

There is also an ongoing discussion in the CSS Working Group about adding this functionality to CSS, even beyond the context of animations.

Pros

  • Allowed in animations and transitions started by the Web Animations API
  • Any number of transformations are allowed
  • Order can be controlled, but depends on which animations have the composite: 'add' option

Cons

  • Still working on spec
  • Only in Firefox Nightly currently
  • Only defined in Web Animations API (at the moment)
  • Less relevant for the non-animated use cases for separating transformations

Are there more?

There surely are other ways to accomplish this (such as doing a lot of calculations in a requestAnimationFrame or specifying all the different combinations possible in CSS), but these three represent the solutions with the least overhead and have the future in mind. It bears repeating that not everything discussed works today and none of these are in every browser that is available.

It doesn’t really bear repeating, though; that’s just how the web works. There is nothing wrong with looking to the future, and seeing how emerging technology is solving the problems of today.