The gotcha with @property

CSS Properties that are typically Hardware Accelerated – e.g. rotate, transform, etc. – don’t run on the Compositor when they rely on a Custom Property that’s being animated.


This content originally appeared on Bram.us and was authored by Bramus!

With @property support being available in Chrome for a long time and now in Safari Technology preview too, it’s time to warn about its big gotcha: when animating registered custom properties, they prevent hardware acceleration for properties that rely on them.

~

# @property 101

@property is an at-rule that allows you to register your CSS Custom Properties. You give them a certain type (syntax), an initial value, and can control whether they should inherit or not. By registering a custom property to be of a certain type, the browser knows how to interpolate its values when used in transitions and animations.

@property --angle {
  syntax: '';
  initial-value: 0deg;
  inherits: false;
}

@keyframes adjust-angle {
  to {
    --angle: 360deg;
  }
}

div {
  --angle: 0deg;
  animation: 10s adjust-angle linear infinite;
  rotate: var(--angle);
}

More details and examples can be found on web.dev and in Exploring @property and its Animating Powers

~

# One CSS Custom Property to rule them all

⚠️ This demo relies on CSS features that are not supported by all browsers yet. For the time being, please use Chrome 111+ (= current Canary) or Safari Technology Preview 162+.

Lets build a demo which animates two aspects of a box at the same time:

  • Rotate the box from 0deg to 360deg
  • Move the box down and up the y-axis over a distance of 100% on each side

Thanks to @property, combined with Individual Transform Properties and Trigonometric Functions, this becomes easy to do. Instead of animating the rotate and translate properties separately, you can animate a --angle custom property from 0deg to 360deg, and use its value in the rotate and translate properties.

@property --angle {
  syntax: '<angle>';
  initial-value: 0deg;
  inherits: false;
}

@keyframes animate {
  from {
    --angle: 0deg;
  }
  to {
    --angle: 360deg;
  }
}

.box {
  animation: animate 5s linear infinite;
  transform-origin: 50% 50%;
  rotate: var(--angle);
  translate: 0 calc(sin(var(--angle)) * 100%);
}

As --angle constantly gets updated, so will the rotate and translate properties that depend on it.

See the Pen Animation in CSS, using a Custom Property by Bramus (@bramus) on CodePen.

For comparison, here is an alternative version that does not use a custom property

@keyframes animate {
  from {
    rotate: 0deg;
    translate: 0 0;
  }
  25% {
    translate: 0 100%;
  }
  50% {
    translate: 0 0;
  }
  75% {
    translate: 0 100%;
  }
  to {
    rotate: 360deg;
    translate: 0 0;
  }
}

.box {
  animation: animate 5s linear infinite;
  transform-origin: 50% 50%;
}

Visually, this code has the same outcome:

See the Pen Animation in CSS, not using a Custom Property by Bramus (@bramus) on CodePen.

Personally I find the first approach – the one using the --angle custom property – easier to grasp, build, and maintain.

This “One CSS Custom Property to rule them all”-approach is a common technique: by simply flipping a few switches you can have your layout respond to it. Take this demo by my colleague Jhey for example: only the --hue value changes, and all stripes of the rainbow respond to that change. Easy.

See the Pen Animated Custom Property by Jhey (@jh3y) on CodePen.

~

# The gotcha

Even though both box-demos both have the same visual outcome, the version that relies on --angle has a problem, as surfaced through a performance inspection:

Performance recording using Chrome DevTools
Timeline recording using Safari Web Inspector

Even though code>rotate and translate are typically properties that are animated on the compositor thread with the help of the GPU, this is not the case here: layout is constantly being trashed and it gets rasterized on every frame!?

Zooming in on the timeline, we see style constantly being invalidated, a successive style recalculation being triggered, and eventually a repaint being done.

Zoomed in timeline

Compare this to a trace of the demo that does not use the Custom Property to drive the animation.

Performance recording using Chrome DevTools
(version without Custom Property)

As the timeline shows, this version is buttery smooth and does not need to constantly recalculate styles – it runs on the compositor, as one would have expected.

~

# But why?

The culprit is the registered --angle custom property that’s being used to control the other properties. Digging into the specification, it becomes clear what goes on:

[T]he value of a registered custom property can be substituted into another value with the var() function. However, registered custom properties substitute as their computed value, rather than the original token sequence used to produce that value.

So as the animation runs, the value of the --angle custom property gets updated on every frame. Because it gets passed as a computed value, style gets invalidated and a new value is computed. Once that’s done, the properties that rely on it require a repaint. Rinse and repeat.

What is interesting here, is what happens when you disable the rotate and translate properties using DevTools. When doing so, repaint stops from triggering but style invalidation still happens by simply having the animation run, even though --angle is not used anywhere.

~

# Can this be fixed?

If the compositor were to be able to do var-substitutions, this could be fixed. In the box example, the compositor would need to figure out a way to prevent the --angle custom property from causing a style invalidation while being animated, thereby preventing everything that follows.

Asking Chromium engineer Rune Lillesveen (futhark), he mentioned that it would require a somewhat deep understanding of such var() substitutions on the compositor – It doesn’t seem to be impossible, but definitely would require a substantial amount of work.

At the time of writing, this optimisation might seem unnecessary, but I guess that’s because usage of @property today is low. My guess is that the need will become more urgent, once Safari and Firefox ship @property as well, and people start actively relying on this.

Either way, go star Chromium issue #1411864 to signal interest if you want to see this improvement happen.

~


This content originally appeared on Bram.us and was authored by Bramus!


Print Share Comment Cite Upload Translate Updates
APA

Bramus! | Sciencx (2023-01-31T23:12:10+00:00) The gotcha with @property. Retrieved from https://www.scien.cx/2023/01/31/the-gotcha-with-property/

MLA
" » The gotcha with @property." Bramus! | Sciencx - Tuesday January 31, 2023, https://www.scien.cx/2023/01/31/the-gotcha-with-property/
HARVARD
Bramus! | Sciencx Tuesday January 31, 2023 » The gotcha with @property., viewed ,<https://www.scien.cx/2023/01/31/the-gotcha-with-property/>
VANCOUVER
Bramus! | Sciencx - » The gotcha with @property. [Internet]. [Accessed ]. Available from: https://www.scien.cx/2023/01/31/the-gotcha-with-property/
CHICAGO
" » The gotcha with @property." Bramus! | Sciencx - Accessed . https://www.scien.cx/2023/01/31/the-gotcha-with-property/
IEEE
" » The gotcha with @property." Bramus! | Sciencx [Online]. Available: https://www.scien.cx/2023/01/31/the-gotcha-with-property/. [Accessed: ]
rf:citation
» The gotcha with @property | Bramus! | Sciencx | https://www.scien.cx/2023/01/31/the-gotcha-with-property/ |

Please log in to upload a file.




There are no updates yet.
Click the Upload button above to add an update.

You must be logged in to translate posts. Please log in or register.