This closing lesson tackles a problem that isn’t obvious until you hit it: staggering cards per row as they scroll into view, while keeping the stagger correct as the grid reflows across breakpoints. It’s a fitting finale for the course, because it leans on almost everything we’ve built — a scroll trigger, the “GSAP toggles a class, CSS animates” pattern, Webflow variables and variable modes — plus one modern CSS function that ties it all together.
The elegant part is how little code it takes: one set action, one utility class, a bit of CSS, and a single variable.
How it works
Why GSAP’s stagger doesn’t fit. You’d expect an animate action with move-Y, opacity, and stagger enabled. But there’s no good target. Point the action at the trigger element and every card animates in isolation — a single element has no siblings to stagger against. Point it at the shared data-animate-item attribute and, the first time it fires, GSAP staggers the entire collection at once, including cards far below the fold. Neither produces a per-row stagger tied to what’s actually entering the viewport.
The strategy: class + CSS delay. So GSAP does the minimum — a set action that adds an in-view class to each card as it scrolls in (scroll interaction, trigger actions, enter → play, threshold when the item’s top hits ~90% of viewport height). The animation lives in CSS: the card’s initial state is opacity: 0 and translateY(20%); the in-view class is the final state (opacity: 1, translateY(0)) plus two transitions (transform + opacity, 800ms, expo). The stagger is then just a per-card transition-delay.
Responsive delay via sibling-index() + a variable. The delay has to depend on a card’s position within its row, and rows change size across breakpoints. Store the column count in a Webflow variable (number-of-columns) with a variable mode per breakpoint (3 desktop / 3 tablet / 2 landscape / 1 portrait). In custom CSS, use the modern sibling-index() function together with that variable to compute each card’s index within the row, then convert it into a transition-delay. Because the formula reads the variable, the same rule re-derives correct delays whenever the grid reflows — fully responsive, no rebuild. The CSS exposes a few friendly knobs: animation-mode (0 = linear, 1 = exponential, in between = a blend), a step/max-delay, and an expo-base for the curve; the “mathematical” section below those can stay untouched.
Staying editable in the Designer. Since the initial state hides the cards, they’d vanish while you build. An override rule shows them whenever Webflow’s runtime JS class isn’t on the html element — a condition only true inside the Designer — so editing stays normal and the live animation is unaffected. (As always, keep the in-view class registered in a style guide so Webflow doesn’t prune it.)
How to use it
-
Layout + trigger. A CMS (or static) grid where each item has
data-animate-item. On the card item, set the initial state:opacity: 0,translateY(20%). -
Scroll interaction. Target
data-animate-item, trigger actions,enter → play, start threshold ~90% viewport height. One set action adding thein-viewclass to the trigger element. -
The in-view state. Define an
in-viewclass withopacity: 1, all move transforms0, and transitions on transform + opacity (800ms, ease-out-expo). Register it in your style guide. -
Responsive column variable. Create a number variable
number-of-columnsand a variable mode per breakpoint with the right values (e.g. 3/3/2/1). -
The stagger engine (CSS). Add an embed with the CSS that targets
[data-animate-item].in-view, computes each card’s row index fromsibling-index()andnumber-of-columns, and sets atransition-delayfrom it. Tuneanimation-mode, the step/max-delay, andexpo-baseto taste. Add the Designer-visibility override. Publish and test across breakpoints.
The stagger CSS (the delay formula and its config block) is pasted from the cloneable — it’s heavily commented there. Copy it and adjust the config values rather than rebuilding the math.