This animation looks far more complicated than it is: a CMS gallery where every image grows from the center of the screen in a staggered sequence, then scatters into a fully responsive grid. The whole scatter is driven by a single animated variable plus a bit of custom CSS math — and once you see how the pieces fit, it becomes a flexible, reusable system.
The real lesson here is how variables, custom CSS, and GSAP stop being separate tools and start working as one. It’s also a great example of CSS quietly reading your CMS data.
How it works
The layout. A CSS grid (pictures_list) with explicit columns and row heights of 100% ÷ rows (e.g. 50% for two rows) — explicit percentages keep it stable where auto gets unpredictable. Spacing comes from padding on each item, not grid gap: a gap added to rows that already sum to 100% would overflow the grid. Each collection item carries data-animate-item and each image data-animate-image, sorted by a CMS order integer field.
CSS reads the CMS. The clever core is an embed inside the collection item, so each item renders its own copy of a small CSS block. That block uses nth-child (tied to the CMS order) and the sibling-index() function so each item knows its own position, then computes its grid coordinates (column/row) and a shift X / shift Y — how far it must move to sit at the center of the grid. Both shifts are multiplied by a move-factor variable, and a translate applies them. At move-factor: 1, everything is pulled to the center; at 0, every shift is 0 and items rest in their real grid cells.
Three variables describe the system. move-factor (the animation driver, starts at 1), columns, and rows. Because the centering math is written in terms of columns and rows, it’s grid-agnostic — change the grid and you only update those numbers.
The GSAP interaction is two actions. On page load: an animate action scaling the images from 0 to their designer scale with a stagger (offset time 0.2s) for the grow-in; then an animate variable action taking move-factor from 1 to 0 for the scatter. The scatter’s start time is pure arithmetic — it must begin exactly when the last image finishes scaling: (itemCount − 1) × stagger + duration. With 12 images: 11 × 0.2 + 0.8 = 3s. GSAP smoothly interpolates the variable, and the CSS does the rest.
Responsiveness is free. Different breakpoints often want different grids. Since columns and rows are variables, add variable modes (tablet, landscape, portrait) with different values (e.g. tablet 4×3, portrait 3×4). The centering math re-reads them per breakpoint, so the same animation just works everywhere — no per-breakpoint rebuild.
How to use it
-
CMS + grid. A
picturescollection with animagefield and an integerorderfield (sort the collection list by it). Build a grid with your columns and set row heights to 100% ÷ rows in grid edit mode. Add padding topictures_itemfor spacing. Tag itemsdata-animate-itemand imagesdata-animate-image. -
Variables. Create number variables
move-factor(1),columns, androwsset to your grid. -
Centering CSS. Add an embed inside the collection item with the CSS that targets
data-animate-itemvianth-child(order), computesshift X/shift Yfromsibling-index(),columns, androws, multiplies both bymove-factor, and applies atranslate. (Paste it from the cloneable — see the note below.) -
Interaction. Page-load GSAP interaction. Action 1: animate
scalefrom 0 ondata-animate-image, 0.8s, power2-out, stagger offset-time 0.2s. Action 2: animate variablemove-factorto 0, 0.8s, power2-out, start =(count − 1) × 0.2 + 0.8. -
Responsive. Add variable modes for the breakpoints where you want a different grid, updating
columns/rows(and the grid + row heights) accordingly.
The custom centering CSS (the
nth-child/sibling-index()math block) is pasted from the cloneable — grab it there and confirm the variable and attribute names match your project.