231Days

231 days since this post was last revised. Specific details have probably changed.

I’ve been quite slow to do any real experimentation with CSS Custom Properties. Unless I’m researching a book or solving a specific problem, these days I tend to let new language features pan out a little, allowing support to get a little deeper before diving in.

However, recently, I had a couple of problems to solve that seemed a perfect fit for CSS Custom Properties. The first requirement was a textbook usecase for Custom Properties; re-themeing with CSS, and it passed with flying colours. The other situation, I’m left wondering if I’m simply missing something. There is further info/thoughts after the summary so I’d love to have your opinion in the comments.

Before we get to the issue, I’m going to very quickly cover CSS Custom Properties, in case you are even later than me to using them.

The TL;DR for Custom Properties is that they are a way to define a value (a string essentially) and then either use ‘as is’ throughout your stylesheets, redefine the property further down the cascade or even set the value via interaction via JavaScript. These values get re-evaluated on the fly so instantly update on page.

To exemplify those cases, here’s how you set a Custom Property on the root of the stylesheet:

:root {
    --sausages: #544021;
}
You use a double dash prior to the custom property name and then assign a value to the property.

This is how you would use that custom property somewhere in your stylesheet:

.thing {
    color: var(--sausages); /* #544021 */
}
The var() being the key thing here. This is how you communicate you want to use a custom property. If you wanted to redefine that same variable down the cascade, you might do this:
.oven {
    --sausages: #3a3021;
}
And then if we had something else that lived in side that amended definition it would inherit that value. For example:
.thing-in-oven {
    color: var(--sausages); /* #3a3021 */
}
Finally, you can also set these vars directly with a simple JavaScript API.
var thing = document.querySelector(".thing");
thing.style.setPropertyValue("--sausages", "#2b2722");
So, var() when you want to use one, and set them with a double hyphen before the custom property name.

Now, there are great posts out there on doing all this fun stuff and far more already. I’d recommend Serg’s over at Smashing.

My problem

As Custom Properties are re-evaluated as they are changed (including all instances of them inside calc expressions) I thought they would be perfect for speed-ramping an animation mid-animation. As far as I can tell, they are not!

In case the term ‘speed ramping’ is alien to you, it’s where you take an something in motion and warp the timing mid-executiion. Think ‘Bullet Time’ from the Matrix.

Imagine an animation looking down on a car as it travels left to right across the page. Once it has accelerated to a constant speed we want the ability to speed it up or slow it down by reducing or increasing the animation duration. If the animation duration gets longer the car needs to slow down. If the animation duration decreases then the animation should speed up to compensate for the now shorter duration it has to complete in.

We want this to happen as the input arrives using JavaScript to set the new value to the Custom Property.

Here is my basic reduction of that principle. Granted, it’s a pretty basic car. Imagine it’s a Lada from the 80s and it won’t seem so bad (yes, it is just an orange square).

See the Pen LeRMvm by Ben Frain (@benfrain) on CodePen.

Here’s the code in that Pen in case I have done something obviously stupid:

<div class="thing"></div>
<button>Add 1s to animation duration</button>
:root {
    --duration: 6s;
}

.thing {
    width: 40px;
    height: 40px;
    background-color: #f90;
    animation-name: move;
    animation-direction: alternate;
    animation-iteration-count: infinite;
    animation-duration: var(--duration);
    animation-timing-function: linear;
    animation-fill-mode: both;
}

@keyframes move {
    0% {
        transform: none;
    }
    100% {
        transform: translateX(100vw);
    }
}
var button = document.querySelector("button");
var root = document.documentElement;
var time = 6;
button.addEventListener("click", function(e) {
    root.style.setProperty("--duration", `${time++}s`);
});
Here is what I see in different browsers:

Chrome (Version 63.0.3239.84)

It does slow down but there is a ‘jump’ as the square repositions.

Safari (Version 11.0 12604.1.38.1.7)

The custom property gets updated in the DOM as the button is clicked but the change doesn’t get applied until you switch tabs or switch focus away from or back to Safari.

Firefox (Version 57.0.1)

Same as Chrome, it does slow down on each click but there is the same ‘jump’.

Summary

These are the kind of issues that usually have me reaching for an animation library like Greensock or Velocity to smooth out. I thought CSS Custom Properties could solve this kind of issue.

Either they can and I’m doing it wrong. Or they can’t and that’s pretty disappointing.

Anyone know for sure?

Update 2.1.17

Bramus offered this explanation in the comments below:

The “jump” you mention seems logical and expected behavior to me. If you you move something (linearly) over a distance of 1000px using a duration of 1 second, then at 0.5s (or 50% of the time when compared against the 1s set) it’ll be at 50% of the distance (500px in this case). If you – at that very moment – extend the duration so that it becomes 2 seconds, the element – still at 0.5s in its animation loop – will be positioned at 25% (or 250px) since 0.5/2 = 0.25.
Bramus!

That certainly explains what I am seeing in Chrome and Firefox but now presents a bigger question.

Is this the most likely/beneficial implementation in this scenario?

If a user is changing the duration value, wouldn’t it be reasonable to expect the speed the item is travelling to change and not the position?

To this end I have asked the question via a couple of bug reports:

https://bugs.chromium.org/p/chromium/issues/detail?id=798732

https://bugs.webkit.org/show_bug.cgi?id=181242