惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

J
Java Code Geeks
F
Fortinet All Blogs
Martin Fowler
Martin Fowler
M
MIT News - Artificial intelligence
G
Google Developers Blog
P
Proofpoint News Feed
Recent Announcements
Recent Announcements
MyScale Blog
MyScale Blog
D
DataBreaches.Net
Stack Overflow Blog
Stack Overflow Blog
月光博客
月光博客
爱范儿
爱范儿
罗磊的独立博客
腾讯CDC
Hugging Face - Blog
Hugging Face - Blog
博客园 - 叶小钗
Vercel News
Vercel News
酷 壳 – CoolShell
酷 壳 – CoolShell
B
Blog
C
Check Point Blog
美团技术团队
宝玉的分享
宝玉的分享
Microsoft Security Blog
Microsoft Security Blog
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻

CSS-Tricks

Let’s Use the Emergent CSS random() Function in all the Browsers | CSS-Tricks What’s !important #18: <geolocation>, Syntax ::highlight()ing, named-feature(), and More | CSS-Tricks Creating Web Widgets Using the Document Picture-in-Picture API | CSS-Tricks MicroLighter: Syntax Highlighter | CSS-Tricks WordPress PHP-Only Block Registration | CSS-Tricks Resolved: CSS Class Prefix Selector | CSS-Tricks CSS Navigation Matching, Early Days | CSS-Tricks WordPress.com Student Plan | CSS-Tricks Dark mode toggles: two states are enough | CSS-Tricks What’s !important #17: Custom Highlight API, CSS Navigation Matching, Fixing text-stroke, and More | CSS-Tricks Blocked aria-hidden: The Warning is Right, and Every Fix You've Found is Wrong | CSS-Tricks Animating CSS border-image | CSS-Tricks SmashingConf Freiburg 2026, September 7-10 | CSS-Tricks Using and Styling the Dialog Element | CSS-Tricks 2026 State of CSS, Devs Surveys | CSS-Tricks Gap Decorations Are Now Available, Here’s What’s New | CSS-Tricks What’s !important #16: sibling-index() Animations, Use Cases for the infinity Keyword, Container Stuck Queries, and More | CSS-Tricks writing-mode | CSS-Tricks pointer-events | CSS-Tricks What’s !important #15: Boundary-aware CSS, Time-based CSS, Full-bleed CSS, and More | CSS-Tricks Get Ready For the Powerful CSS border-shape Property! | CSS-Tricks What’s !important #14: Gap Decorations, random(), <select> field sizing, and More | CSS-Tricks The Shifting Line Between CSS States and JavaScript Events | CSS-Tricks translateZ() | CSS-Tricks translateY() | CSS-Tricks translateX() | CSS-Tricks translate() | CSS-Tricks Using Scroll-Driven Animations for Opposing Scroll Directions | CSS-Tricks A First Look at Scroll-Triggered Animations | CSS-Tricks The Siren Song of  ariaNotify() | CSS-Tricks
animation-trigger | CSS-Tricks
Saleh Mubashar · 2026-08-26 · via CSS-Tricks

Experimental: Check browser support before using this in production.

The CSS animation-trigger property delays the start of a CSS animation until a specific trigger occurs. More specifically, it listens for a named trigger and controls how the animation plays or pauses in response.

.element {
  animation: fade-in 0.35s ease-in-out both;
  animation-trigger: --trigger play-forwards play-backwards;
}

This behavior has traditionally been JavaScript territory, typically with the Intersection Observer API.

Scroll-triggered animations should not be confused with scroll-driven animations. Although both rely on scroll or view timelines, they are fundamentally different concepts. We’ll get to those differences in a minute.

The animation-trigger property is defined in the Animation Triggers specification

Syntax

animation-trigger: none | <trigger-name> <enter-action> [<exit-action>];
  • Initial value: none
  • Applies to: all elements
  • Inherited: no
  • Computed value: as specified
  • Animation type: not animatable

Values

The property accepts none, or a comma-separated list of triggers and their corresponding actions:

  • none: The default state. The animation behaves normally and is not a triggered animation.
  • <trigger-name>: The dashed ident (e.g., --fade-in-trigger) of the trigger you want to listen to. This must match a name defined by the timeline-trigger or event-trigger properties.
  • <animation-action>: One or more keywords dictating what the animation should do when the trigger activates (and optionally, when it deactivates). This can be further divided into:
    • <enter-action>: Dictates what happens when the trigger state becomes “active”.
    • <exit-action> (optional): Dictates what happens when the trigger state returns to “inactive”. By default, this is none.

The “trigger” in animation-trigger can refer to either timeline-based triggers, such as scroll or view progress timelines, or event-based triggers like DOM events (e.g., a click). In this entry, we’ll mainly focus on timeline triggers. If you’re curious about event-based triggers, you can check out the official spec.

By default, trigger names have a global scope. If multiple elements define the same trigger name, the element that comes later in the cascade gets chosen. You can restrict the scope of a trigger to a specific DOM subtree with the trigger-scope property.

Animation Actions

  • play-forwards: Sets the playback rate to positive and plays the animation.
  • play-backwards: Sets the playback rate to negative and plays the animation in reverse.
  • play: Simply plays the animation at its current rate.
  • play-once: Plays the animation only from its initial or paused state. It ignores the trigger if the animation has already finished playing.
  • pause: Freezes the animation in place.
  • reset: Instantly sets the animation progress back to 0 and pauses it.
  • replay: Sets the progress back to 0 and immediately plays it.
  • none: Does nothing.

Some actions aren’t <enter-action> or <exit-action> only, meaning it’s possible to play-backwards when entering and play-forwards when exiting.

Timeline Triggers

To actually use animation-trigger, you’ll typically need to set up a timeline trigger first. That controls when an animation starts based on where an element is within a timeline (like scroll or viewport position). More specifically, it activates when the element enters a defined activation range within that timeline.

To set up your timeline trigger, you’ll first define a custom <trigger-name> (like --fade-in) to link it to your animation-trigger , followed by a <source> timeline like a view() or scroll() function.

timeline-trigger-name: --fade-in;
timeline-trigger-source: view();

Then, you specify the <activation-range> (such as contain) to dictate exactly when the trigger turns “on” inside the viewport.

timeline-trigger-activation-range: contain;

You can also provide an optional <active-range> to define the outer boundary where the trigger stays active before turning “off”, though if you leave it out, the browser just defaults to your activation range.

timeline-trigger-active-range: cover;

To get a better feel for how this works, check out the timeline ranges visualizer from the Chrome team.

If you use different ranges, the active range must include the activation range; otherwise, the trigger can’t turn “on.”

While you can use all the individual longhand properties:

  • timeline-trigger-name
  • timeline-trigger-source
  • timeline-trigger-activation-range
  • timeline-trigger-active-range

…you’ll almost always want to use the shorthand instead:

timeline-trigger: none | <trigger-name> <source> <activation-range> [ / <active-range>];

Unlike many CSS shorthands (like background or border), the order of values matters here, so you can’t rearrange them freely.

It’s worth noting that triggers and animations don’t have to be on the same element. You can define the timeline-trigger on a parent and apply the animation-trigger to multiple child elements. When the parent enters view, all children can animate together.

Using triggers

Let’s create a simple text reveal animation. We’ll use an element as the trigger point. Once you scroll past it, the animation plays and the text fades in.

First, define a timeline trigger on the trigger element:

.trigger {
  timeline-trigger: --trigger scroll() contain / cover;
}

This sets up a trigger named --trigger that activates based on scroll position. The range contain / cover means the animation is triggered when the element is fully visible in the scrollport (contain) and continues to run as long as any part of it remains visible in the scrollport (cover).

Next, we’ll apply the animation-trigger to the text you want to animate, along with the animation itself:

.text {
  animation-trigger: --trigger play;
  animation: fade 0.6s ease-out;
}

The cool part is that you can mix and match different animation-action values to get very different behaviors using the same trigger. Here’s a quick demo showing the same setup with different animation actions:

With scroll-driven animations, the animation’s progress is directly tied to scroll position. As you scroll, the animation scrubs forward or backward in sync with the timeline. There’s no concept of a “start” or “fire” moment.

In contrast, scroll-triggered animations are state-based rather than continuous. A trigger has a binary state and when a condition is met—such as an element entering a defined range—the trigger fires an associated action (like playpause, or reset). Once triggered, the animation behaves like your regular CSS animations with no link to the scroll progress.

Here’s a pretty cool demo to see the differences between the two, courtesy of utlitybend:

Specification

The animation-trigger property is defined in the Animation Triggers specification, which is currently in Editor’s Drafts. That means the information can change between now and what it becomes an official Candidate Recommendation.

Browser Support

Just Chrome 145+ at the time of this writing.

Further Reading