You can use either CSS transitions or animations. I'll show examples of how to use both below.
In this case I recommend using an animation over a transition. It seems more straight-forward and easier to implement.
Animations
Technically, animations can do everything that transitions can. They can easily be reused on multiple elements.
But 'classic' CSS animations have rather 'rigid' keyframes as a trade-off: (Apart from using custom properties) values in keyframes cannot be set for each element individually.
Additionally, each CSS animation requires a unique name. With many animations, this may be difficult to realize in an organized way.
You can also animate with the Web Animations API (more in the section below). Such JavaScript animations do not come with the above limitations.
CSS Animation
A property's declared value will be the implied initial or final value in an animation. But the initial and final values can also be explicitely declared in the from
(or 0%
) and to
(or 100%
) keyframes, respectively.
By declaring font-size: 150%
in the element's rule, 150% will be the implied value 'from' and 'to' value in an animation. Since we want to animate from value 200% to 150%, we can:
Example with implied final value:
@keyframes from-doubled-font {
from {font-size: 200%}
}
p {
font-size: 150%;
animation: from-doubled-font 1s linear;
}
<p>This is some text!</p>
Web Animation
We can also animate with the Web Animations API in JavaScript. As an API for JavaScript, it allows animations to be more dynamic (in terms of keyframes, property values and animation options).
Using the Web Animations API may be more verbose, but is more readable than the short-hand animation
property due to explicitely naming all properties.
When passing an array containing only a single keyframe, that keyframe will be the to
keyframe. This means we have to pass at least two keyframes to declare the 'from' properties.
Example of using the Web Animations API:
const p = document.querySelector("p");
const from = {fontSize: "200%"};
const to = {}; // Use implicit final value
const keyframes = [from, to];
const options = {
duration: 1000, // ms
easing: "linear"
};
p.animate(keyframes, options);
p {font-size: 150%}
<p>This is some text!</p>
Transitions
Transitions allow transitioning from one value to another (one change). This limits their use cases but also makes them easy to declare, even on multiple elements.
Since we want font-size
to change from the initial 150% to 200% and then transition to 150% again (which is two changes), we cannot simply use a transition.
Instead, we will have to use JavaScript and cause a reflow, so that the intermediate 200% will be seen as the 'transition-from' value.
Causing immediate reflows
We require a reflow before declaring the 'transition-to' value. There are multiple causes for reflowing, some of which we can cause immediately ourselves (e.g. by reading offsetHeight
).
By declaring font-size: 200%
and then cause an immediate reflow, we basically 'commit' the 200% as the 'current state'. After that, we can start the transition by declaring the transition and redeclaring font-size
, which will be the 'transition-to' value.
Example:
const p = document.querySelector("p");
p.style.transition='none';
p.style.fontSize='200%';
reflow();
p.style.transition='font-size 1s linear';
p.style.fontSize='150%';
function reflow() {
// Example implementation:
document.body.clientHeight;
}
p {font-size: 150%}
<p>This is some text!</p>
Waiting for reflows
Instead of causing immediate reflows, we can also wait for them. We can cause a reflow to happen e.g. by changing styles.
Repainting is always preceded by a reflow. By waiting for a repaint, we can wait for a reflow. We can do this with the requestAnimationFrame()
function, whose callback will be called right before a repainting's reflow.
Calling requestAnimationFrame()
from within a callback to requestAnimationFrame()
will wait for the subsequent repaint's reflow.
This means we can write a helper function to wait for frames, e.g.:
function waitFrames(n, callback) {
if (n > 0) {
requestAnimationFrame(() => waitFrames(n - 1, callback));
} else {
requestAnimationFrame(callback);
}
}
Example transition after waiting for a reflow:
const p = document.querySelector("p");
p.style.fontSize = "200%";
waitFrames(1, function afterWaiting() {
p.style.transition = "font-size 1s linear";
p.style.fontSize = "150%";
});
function waitFrames(n, callback) {
if (n > 0) {
requestAnimationFrame(() => waitFrames(n - 1, callback));
} else {
requestAnimationFrame(callback);
}
}
p {font-size: 150%}
<p>This is some text!</p>