animation-range

John Rhea on Updated on

The CSS animation-range property lets you control how a view timeline animation works, setting exactly when and where the animation starts and stops. You can think of it as letting you pin the start and end of your animation to different points in the view timeline.

.element {
  animation-range: cover;
}

Allowing the animation to “cover” the timeline, the animation begins as the user scrolls the element into the viewport and ends as the user scrolls the element out of the viewport:

So, for example, the spinning assistant in the following demo starts spinning as soon as her first pixel enters the viewport and comes to a stop as her last pixel exits the viewport. Also the magician begins to disappear as he starts to exit the viewport (because zombies never do anything the way they intend… lacking braaains and all…)

Here’s a video in case your browser does not yet support view timeline animations:

That’s the basic idea. Let’s get into more details and examples and really poke at how this works because there’s a lot of nuance to consider.

Note: When we discuss an element “scrolling into view,” we’re discussing the downward scrolling motion where an element comes from the bottom of the screen and exits at the top of the screen. If we scroll up the animation runs in reverse.

Syntax

The formal syntax looks like this:

animation-range = 
  [ <'animation-range-start'> <'animation-range-end'>? ]###  

<animation-range-start> = 
  [ normal | <length-percentage> | <timeline-range-name> <length-percentage>? ]###  

<animation-range-end> = 
  [ normal | <length-percentage> | <timeline-range-name> <length-percentage>? ]###  

<length-percentage> = 
  <length> |
  <percentage>

…which is really just a fancy way of saying:

  • The property is a shorthand for two other constituent properties, animation-range-start and animation-range-end, meaning it takes up to two values. In practice, however, it looks like it takes up to four values (more on this in a bit).
  • Those two values can be expressed as keywords (e.g. cover) or a length (e.g. 50vh, 50%, etc.).
  • A length can be any CSS number, including percentages.

If one value is used, the value applies to both animation-range-start and animation-range-end If two values are used, the first is for animation-range-start and the second is for animation-range-end. (There’s a nuance to this where it can look like we’re using two values when it’s actually one value because we’re using a keyword and a length as a single value. More on this later.)

  • Initial value: normal
  • Applies to: all elements
  • Inherited: no
  • Percentages: relative to the specified named timeline range if one was specified, else to the entire timeline.
  • Computed value: list, each item either the normal keyword or a timeline range and progress percentage.
  • Animation type: n/a.

Values

/* Keywords */
animation-range: normal;
animation-range: cover;
animation-range: contain;
animation-range: entry;
animation-range: exit;

/* Lengths and percentages */
animation-range: 200px;
animation-range: 10%;

/* Two value syntax */
animation-range: contain 200px;
animation-range: cover 10%;

/* "Three" value syntax: more on that later */
animation-range: cover cover 200px;
animation-range: 10% exit 90%;
animation-range: entry 10% 90%;

/* "Four" value syntax: more on that later */
animation-range: cover 0% cover 200px;
animation-range: entry 10% exit 100%;

Keywords

The animation-range property (and its constituent properties) accept the following keyword values:

  • normal: **This is the default and for reasons we’ll discuss shortly, behaves like cover.
  • cover: This value “covers” the viewport. This is exactly what we discussed above where the animation starts as soon as the first pixel of the animated element scrolls into the viewport and it ends when the last pixel leaves the viewport.
  • contain: The animation is “contained” by the viewport, i.e., the animation starts once the element has fully scrolled into the viewport and ends as the first pixel of the element scrolls out of the viewport.
  • entry: This pins the start of the animation to when the first pixel of the animated element scrolls into the viewport and the end to when the last pixel of the animated element scrolls into view or the element reaches the top of the viewport, whichever comes first.
  • entry-crossing: Similar to entry, but handles an edge case a little differently. If the image is larger than the viewport, it pins the end of the animation to when the last pixel of the element scrolls into view.
  • exit: Similar to entry, but for leaving. This pins the start of the animation to when the first pixel of the animated element scrolls out of the screen or when the last pixel of the animated element scrolls into the viewport, whichever is later. And it pins the end to when the last pixel of the element scrolls out of the viewport.
  • exit-crossing: Again, handles the case when an animated element is taller than the viewport. This pins the animation start to the first pixel that scrolls out of the viewport so that the animation starts as the element begins to exit. It does not wait for the element to be fully within the screen to start the timeline.

Play around with these keywords in action:

All of these keywords — with the exception of normal — aren’t just keywords, but the names of timeline ranges, i.e., a segment of a timeline. With animation-range you are actually attaching your keyframes to a portion of a timeline. So, while I initially said that it was a way of attaching the beginning and end of your animation, it’s more specifically a way to attach your keyframes to a segment of the timeline.

Lengths

You can set a length or percentage to attach your animation to as well. You can also fine-tune any of the above timeline ranges using a length-percentage to determine where the keyframe should be attached within the timeline range and where it should start or end, depending on whether it modifies animation-range-start or animation-range-end values. Percentages are probably the most flexible/responsive, but finite units like 200px or 3em are also permissible.

More on normal (assuming normality exists)

The normal value is not a timeline range name, per se, so it is not modifying where the keyframes are attached, but rather it is a default. The cover value is the default timeline range, so normal is really just another way of saying cover.

The main difference between normal and cover is that you cannot use a length percentage (e.g. 50%) when declaring normal. For clarity’s sake and so that future-you doesn’t wonder what the heck past-you was thinking, if you’re using cover, I’d recommend explicitly writing “cover,” don’t leave it as normal. Future-you will thank me and/or past-you for listening to me. (Now if I could only get past/present-me to stop pounding sleeves of Double Stuff Oreos, future-me might be happier, healthier, and less often in food comas… but I digress.)

Here’s what I mean. If we were to declare normal as the first value, then any length percentage value we put after it applies not to the animation-range-start value, as it would using a timeline range, but to the animation-range-end value instead:

animation-range: normal 90%; /* 90% applies to animation-range-end */

The two (but really four)-value syntax

A “single” value can be a keyword on its own, a percentage on its own, or split into two pieces: a keyword plus a percentage. So, the following are all considered “two”-values:

/* Two keywords */
animation-range: cover exit;
/* Two lengths */
animation-range: 20% 70%;
/* Two keywords with a length applied to the first keyword */
animation-range: contain 20% enter;
/* Two keywords with a length applied to the second keyword */
animation-range: contain enter 70%;
/* Two keywords with lengths applied to each one */
animation-range: cover 20% exit-crossing 70%;

That’s why we say the animation-range property takes up to two values: one representing animation-range-start and animation-range-end. But in practice, it looks like it takes up to four values since those constituent properties can each be split into two values.

When using just keywords, 0% and 100% are assumed for animation-range-start and animation-range-end, respectively. Thus, animation-range: cover is equivalent to animation-range: cover 0% cover 100% and animation-range: contain exit is equivalent to animation-range: contain 0% exit 100%.

Declaring timeline names directly in CSS keyframes

You can actually declare the timeline range names directly to your keyframes:

@keyframes animate-in {
  entry 0% { opacity: 0; transform: translateY: 100%; }
  entry 100% { opacity: 1; transform: translateY: 0%; }
}
@keyframes animate-out {
  exit 0% { opacity: 1; transform: translateY: 0%; }
  exit 100% { opacity: 0; transform: translateY: 100%; }
}

.element {
  animation: animate-in linear forwards,
             animate-out linear forwards;
  animation-timeline: view();
}

Notice that animation-range is no longer needed since its values are declared in the keyframes. That’s a nice little way to write your timeline animations more explicitly.

It works with Scroll Timelines

Thanks to Bramus for noting that we can use the animation-range property with scroll-driven animations in addition to view transitions:

For example: animation-range: 20% 80% with a ScrollTimeline will start the animation at 20% of the scroll distance, and by the time you hit 80% of the scroll distance the animation will have completed.

On that same note, the animation-range and view-timeline-inset properties can be used to do very similar things. Compare and contrast their settings in this demo:

Demo

The following demo allows you to configure the animation-range with different keyword and length values.

Note the parachutist character in the demo. Its animation transforms the element. And when you transform an element, the element is not pulled out of the flow of the document, but is instead rendered on a separate layer and a placeholder of sorts stays in the element’s original place. This prevents the performatively expensive repainting and layout changes that would otherwise be required.

When using this type of animation though, the entrance and exit of the placeholder is what triggers the animation rather than where the transformed element is on the screen. For instance, if you have scaled content — whether it’s scaled up or down — the animation is pegged to when the element at its “normal” scale (scale: 1) enters or exits the viewport.

Browser support