Changing the speed of an ongoing transition
A progress bar animation can be simple or challenging depending on how complex the animation is.
One example I had to build at work was quite simple.
There is a div inside another div. Parent div provides the background and hides overflows, child div has the exact dimensions as the parent but translates according to the progress values. We could change the width but translate lets you reduce layout events in the critical rendering path (opens in a new tab). Anytime we want to update the progress value we can update the translate value and the bar will fill up as defined by the CSS transition.
The actual requirement for this progress bar was to slowly progress for a minute while an async task was running and then hit 100 as soon as the task finished.
We remove the interval and set translate to 0, that way it looks like the transition sped up from the moment the async task is completed. In this particular case, the progress bar fills up at a constant speed and then speeds up when the async task is finished. Speeds up instead of fills up because abruptly changing the bar to be filled 100% isn't as appealing. The entire animation looks smooth because the transition timing function duration is the same setInterval
interval. In a larger application, we can't guarantee this to happen in perfect synchronisation. We want the javascript to handle just the change of speeds and CSS to control the animation.
If we use CSS animation
something interesting happens
If you use the same animation sequence with different timings. The sequence doesn't play from where it was before the animation configuration changed, it immediately goes to whatever state the sequence would've been in if it was the original configuration at the start. In the codepen example above, there are two sequences defined to prevent this from happening. Both sequences are technically same. Even then we don't get what we want. The animation gets replaced instead of changing the configuration and resuming from the state it was in.
Using the javascript solution had given us the ability to resume, at least after each individual 500ms transition. CSS animations don't. We could cook up some smart code that uses the translate value when we switch the speed but we did tell ourselves to reduce the dependency on js. For those still interested, you can refer Controlling CSS Animations and Transitions with JavaScript (opens in a new tab)
We can try changing the transition when we want to speed it up but new values for transition are ignored, meaning even if we speed it up, we will still see the old transition. Fortunately, there's a way to ensure that the new transition values are followed when we make a change. The trick is to change the property we are transitioning as well.
Now, setting the final translate value to anything other than 0 is unacceptable to a lot of people for two reasons. One, the final state of the progress bar is visually different because of the 1px difference and two, the 1px value in the code doesnt feel right as we want the progress bar to fill up fully if the speed up is not triggered. There are two solutions.
- The difference between old value and new value of the transitioned property can be made abismally small, like
0.01px
- Use a different unit, changing from
0%
and0px
, also updates the transition
There you have it, we managed to update a transition before it was completed.
For those interested in CSS pixel values, the following articles can be helpful