ScrollTrigger.batch()
ScrollTrigger.batch() is a static method that creates one ScrollTrigger per element but groups the ones entering the viewport in the same frame into a single callback, so you can stagger a whole batch at once instead of animating each element on its own trigger. It's the right tool when a grid of cards scrolls into view together and you want them to cascade rather than pop in one by one.
Updated July 1, 2026
How ScrollTrigger.batch works
You call ScrollTrigger.batch(targets, vars) where targets is a selector string or an array of elements. It builds one ScrollTrigger per target, then collects every element that fires the same callback (like onEnter) within a short interval and hands them to you as a group. The default interval is about one animation frame, so elements that cross the start line together arrive in the same call.
The batched callback signature is different from a normal ScrollTrigger callback. Instead of one instance, you get two arguments: the array of elements that fired, and the array of their scrollTriggers. You animate the whole array in one gsap.to() with a stagger, which is where the cascade comes from.
Two options tune how the grouping behaves. interval is the max time in seconds to keep collecting elements before the callback fires (default is roughly one requestAnimationFrame). batchMax caps how many elements land in a single batch, so a long column doesn't stagger 40 items in one run. Pass batchMax a function that returns a number if the count should change with breakpoint, it re-runs on refresh.
Some vars are off-limits
batch() manages its own triggers, do not pass trigger, animation, scrub, snap, toggleActions, or invalidateOnRefresh in the vars. You drive the animation yourself inside the callbacks instead. It returns an array of the created ScrollTrigger instances if you need to kill() them later.Use it for
- A grid of cards or tiles that should cascade in as the section scrolls into view
- Long lists where a per-element reveal would fire dozens of separate, uncoordinated tweens
- Portfolio and gallery layouts where rows enter together and you want them staggered by arrival, not by DOM order
- Any reveal where the count of visible elements changes with the breakpoint and you want the stagger to adapt
Use something else when
- You have one element or one section, use a plain
scrollTriggeron a single tween, batching buys you nothing - The elements are all on screen at load (above the fold), use a normal timeline with
staggerand skip the scroll trigger - You only need visibility detection and no GSAP animation,
IntersectionObserveris smaller and has no GSAP dependency - You want the whole group to move as scroll scrubs rather than fire-and-forget on enter, use a single scrubbed ScrollTrigger over a timeline instead
Used in these Annnimate components
The pattern shows up wherever a set of elements needs to reveal together on scroll without each one running its own isolated tween:
- The Animated Grid cascades its tiles in as the grid enters the viewport, staggering the batch that arrives in the same frame instead of triggering every tile separately
- The Element Reveal component staggers a group of items on scroll-in, the same batch-then-stagger shape covered here
Why batch instead of a trigger per element
opacity and transform (y) across the batch and let the compositor handle it.See it running in production
Common questions
- What's the difference between ScrollTrigger.batch and a normal ScrollTrigger with a stagger?
- A normal ScrollTrigger fires on one trigger element and can stagger the children it animates, but all those children reveal at the SAME scroll position regardless of where they sit on screen. ScrollTrigger.batch creates a trigger per element, so each element reveals when it individually crosses the start line, and elements crossing together get grouped into one staggered call. Use batch when the elements enter the viewport at different scroll positions but you still want the ones arriving together to cascade.
- Why do my batch callbacks get two arguments instead of one?
- Batched callbacks have a different signature from regular ScrollTrigger callbacks. A normal onEnter receives the single ScrollTrigger instance. A batched onEnter receives the array of elements that fired within the interval as the first argument, and the array of their ScrollTrigger instances as the second. You pass the first argument straight into gsap.to() to animate the whole group.
- How do I control how many elements land in one batch?
- Set batchMax to a number. When the batch fills to that many elements the callback fires and a new batch starts collecting. This stops a long column from staggering 40 items in a single run. For layouts where the visible count changes by breakpoint, pass batchMax a function that returns a number, GSAP re-runs it on refresh (resize, tab focus).
- Do I still need to register ScrollTrigger to use batch?
- Yes. batch is a static method on the ScrollTrigger plugin, so you need
import { ScrollTrigger } from 'gsap/ScrollTrigger'andgsap.registerPlugin(ScrollTrigger)once at module load. In Next.js wrap the registration inif (typeof window !== 'undefined')so it only runs client-side. - Is ScrollTrigger.batch a paid or Club GSAP feature?
- No. GSAP has been 100% free including every plugin and commercial use since version 3.13 (mid-2024, sponsored by Webflow). ScrollTrigger and its batch method are part of the free ScrollTrigger plugin, nothing is gated.
