CMS Grow & Scatter Grid Animation in Webflow

Advanced 24:53 webflowgsapcmsvariablesgrid

Build a CMS grow-and-scatter gallery in Webflow: images stagger from the center then settle into a responsive grid, driven by one variable, CSS math, and GSAP.

Key takeaways

  • The whole scatter is driven by one number variable (move-factor): the centering CSS multiplies each item's shift by it, so animating it from 1 to 0 melts the offsets away and items settle into the grid.
  • CSS can read CMS data — an embed inside the collection item uses nth-child(order) and sibling-index() so each item knows its own position and computes its grid coordinates.
  • Set grid row heights explicitly to 100% ÷ rows (not auto) for a stable layout, and create spacing with item padding instead of grid gap, which would break percentage-based row heights.
  • Describe the layout in variables (columns, rows) so the centering math is grid-agnostic — change the grid and just update the numbers.
  • Time the scatter to begin exactly when the last image finishes scaling: intervals × stagger + duration (e.g. 11 × 0.2 + 0.8 = 3s).
  • Variable modes make it responsive for free: give columns and rows different values per breakpoint and the same animation just works.

Video chapters

  1. 00:00 Intro
  2. 01:09 Breaking down the CMS collection and page structure
  3. 07:18 Adding the Webflow variables
  4. 09:04 Breaking down the custom CSS
  5. 13:48 Building the GSAP interaction
  6. 19:04 Testing flexibility and responsiveness
  7. 23:44 Outro

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

  1. CMS + grid. A pictures collection with an image field and an integer order field (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 to pictures_item for spacing. Tag items data-animate-item and images data-animate-image.

  2. Variables. Create number variables move-factor (1), columns, and rows set to your grid.

  3. Centering CSS. Add an embed inside the collection item with the CSS that targets data-animate-item via nth-child(order), computes shift X/shift Y from sibling-index(), columns, and rows, multiplies both by move-factor, and applies a translate. (Paste it from the cloneable — see the note below.)

  4. Interaction. Page-load GSAP interaction. Action 1: animate scale from 0 on data-animate-image, 0.8s, power2-out, stagger offset-time 0.2s. Action 2: animate variable move-factor to 0, 0.8s, power2-out, start = (count − 1) × 0.2 + 0.8.

  5. 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.

Resources

Frequently asked questions

How can one variable animate a whole grid of items to different positions?
The centering CSS calculates each item's horizontal and vertical shift and multiplies both by a shared move-factor variable. At factor 1 the items are pulled to the center; animate the factor to 0 and every shift becomes 0, so all items settle into their real grid cells at once — one variable, many positions.
Can CSS read a CMS field in Webflow?
Indirectly, yes. Put an embed with CSS inside the collection item so each item renders its own copy, and use nth-child tied to the CMS order field (plus the sibling-index() function) so each instance knows its position in the list and computes its own grid coordinates.
Why set grid row heights to a percentage and use padding instead of grid gap?
Explicit row heights of 100% ÷ number-of-rows keep the layout stable where auto can get unpredictable. A grid gap on top of rows that already total 100% would overflow the grid and break it, so spacing is created with padding on each item instead.
How do I make this grow-and-scatter animation responsive?
Store the grid's columns and rows as Webflow variables and add variable modes for each breakpoint with different values (e.g. tablet 4×3, portrait 3×4). Because the centering math reads those variables, the same animation adapts to every layout without rebuilding the interaction.

← Back to the course