v146 · CSS · Basic Trigger Demo

Basic Trigger Demo

Scroll down to see cards animate in. Chrome 146 uses animation-trigger: view() to fire the animation the moment an element enters the scrollport — purely in CSS. Older browsers get an IntersectionObserver fallback that adds an .in-view class.

Chrome 146 required for native animation-trigger. On older browsers this demo uses a JavaScript IntersectionObserver fallback so the animations still work — scroll down to test.
detecting support…
↓ Scroll down to see the cards animate in ↓

single cards

Card 1 — Fade and slide up

This card fades in and slides up when it enters the viewport. The animation plays once and stays at its end state.

animation-trigger: view(block 10% 90%)

Card 2 — Same animation

Each card has the same animation-trigger: view() declaration. The browser fires each animation independently as the element scrolls into view.

animation-trigger: view(block 10% 90%)

Card 3 — No JavaScript required

In Chrome 146, this is pure CSS — no IntersectionObserver, no class toggling. The browser handles the scroll detection and animation scheduling on the compositor thread.

animation-trigger: view(block 10% 90%)

staggered grid

Six cards in a 2-column grid. Odd columns trigger immediately, even columns get a 100 ms delay — creating a gentle stagger with no JavaScript coordination needed.

Grid A

First column, no delay.

delay: 0s

Grid B

Second column, 100 ms delay.

delay: 0.1s

Grid C

First column, no delay.

delay: 0s

Grid D

Second column, 100 ms delay.

delay: 0.1s

Grid E

First column, no delay.

delay: 0s

Grid F

Second column, 100 ms delay.

delay: 0.1s

CSS (native + fallback)

/* The animation */
@keyframes fade-slide-up {
  from { opacity: 0; translate: 0 2.5rem; }
  to   { opacity: 1; translate: 0 0; }
}

/* Fallback: start hidden, JS adds .in-view class */
.card {
  opacity: 0;
  translate: 0 2.5rem;
  transition: opacity 0.5s ease, translate 0.5s ease;
}
.card.in-view {
  opacity: 1;
  translate: 0 0;
}

/* Native scroll trigger — Chrome 146+ */
@supports (animation-trigger: view()) {
  .card {
    opacity: 1; /* reset — animation handles it */
    translate: 0 0;
    transition: none;
    animation: fade-slide-up 0.55s ease both;
    animation-trigger: view(block 10% 90%);
  }
}

/* Stagger via animation-delay */
.grid .card:nth-child(even) { animation-delay: 0.1s; }

JavaScript fallback

// Only runs when animation-trigger is not supported
if (!CSS.supports('animation-trigger', 'view()')) {
  const observer = new IntersectionObserver(entries => {
    for (const entry of entries) {
      if (entry.isIntersecting) {
        entry.target.classList.add('in-view');
        observer.unobserve(entry.target); // once only
      }
    }
  }, { threshold: 0.1 });

  document.querySelectorAll('.card').forEach(el => observer.observe(el));
}

see also