đ A gentle invitation: Explore transformation through the Dance Entropy celebration.
Watch the lyric video here or visit
đŚÎŚwlchemyđŚâ⏠to learn more.
Thank you for your understanding and support.
Youâve probably seen this kind of scroll effect before, even if it doesnât have a name yet. (Honestly, we need a dictionary for all these weird and wonderful web interactions. If youâve got a talent for naming thingsâŚdo it. Seriously. The internet is waiting.)
Imagine a grid of images. As you scroll, the columns donât move uniformly but instead, the center columns react faster, while those on the edges trail behind slightly. It feels soft, elastic, and physical, almost like scrolling with weight, or elasticity.
You can see this amazing effect on sites like yzavoku.com (and Iâm sure thereâs a lot more!).
So what better excuse to use the now-free GSAP ScrollSmoother? We can recreate it easily, with great performance and full control. Letâs have a look!
What Weâre Building
Weâll take CSS grid based layout and add some magic:
Inertia-based scrolling using ScrollSmoother
Per-column lag, calculated dynamically based on distance from the center
A layout that adapts to column changes
HTML Structure
Letâs set up the markup with figures in a grid:
 Â
   Â
    Zorith - L91
 Â
 Â
Inside the grid, we have many .grid__item figures, each with a background image and a label. These will be dynamically grouped into columns by JavaScript, based on how many columns CSS defines.
CSS Grid Setup
.grid {
  display: grid;
  grid-template-columns: repeat(var(--column-count), minmax(var(--column-size), 1fr));
  grid-column-gap: var(--c-gap);
  grid-row-gap: var(--r-gap);
}
.grid__column {
  display: flex;
  flex-direction: column;
  gap: var(--c-gap);
}
We define all the variables in our root.
In our JavaScript then, weâll change the DOM structure by inserting .grid__column wrappers around groups of items, one per colum, so we can control their motion individually. Why are we doing this? Itâs a bit lighter to move columns rather then each individual item.
JavaScript + GSAP ScrollSmoother
Letâs walk through the logic step-by-step.
1. Enable Smooth Scrolling and Lag Effects
gsap.registerPlugin(ScrollTrigger, ScrollSmoother);
const smoother = ScrollSmoother.create({
  smooth: 1, // Inertia intensity
  effects: true, // Enable per-element scroll lag
  normalizeScroll: true, // Fixes mobile inconsistencies
});
This activates GSAPâs smooth scroll layer. The effects: true flag lets us animate elements with lag, no scroll listeners needed.
This method groups your grid items into arrays, one for each visual column, using the actual number of columns calculated from the CSS.
3. Create Column Wrappers and Assign Lag
const buildGrid = (columns, numColumns) => {
const fragment = document.createDocumentFragment(); // Efficient DOM batch insertion
const mid = (numColumns - 1) / 2; // Center index (can be fractional)
const columnContainers = [];
// Loop over each column
columns.forEach((column, i) => {
const distance = Math.abs(i - mid); // Distance from center column
const lag = baseLag + distance * lagScale; // Lag based on distance from center
const columnContainer = document.createElement('div'); // New column wrapper
columnContainer.className = 'grid__column';
// Append items to column container
column.forEach((item) => columnContainer.appendChild(item));
fragment.appendChild(columnContainer); // Add to fragment
columnContainers.push({ element: columnContainer, lag }); // Save for lag effect setup
});
grid.appendChild(fragment); // Add all columns to DOM at once
return columnContainers;
};
The lag value increases the further a column is from the center, creating that elastic âcatch upâ feel during scroll.
4. Apply Lag Effects to Each Column
const applyLagEffects = (columnContainers) => {
columnContainers.forEach(({ element, lag }) => {
smoother.effects(element, { speed: 1, lag }); // Apply individual lag per column
});
};
ScrollSmoother handles all the heavy lifting, we just pass the desired lag.
5. Handle Layout on Resize
// Rebuild the layout only if the number of columns has changed on window resize
window.addEventListener('resize', () => {
const newColumnCount = getColumnCount();
if (newColumnCount !== currentColumnCount) {
init();
}
});
This ensures our layout stays correct across breakpoints and column count changes (handled via CSS).
Now, thereâs lots of ways to build upon this and add more jazz!
For example, you could:
add scroll-triggered opacity or scale animations
use scroll velocity to control effects (see demo 2)
adapt this pattern for horizontal scroll layouts
Exploring Variations
Once you have the core concept in place, there are four demo variations you can explore. Each one shows how different lag values and scroll-based interactions can influence the experience.
You can adjust which columns respond faster, or play with subtle scaling and transforms based on scroll velocity. Even small changes can shift the rhythm and tone of the layout in interesting ways. And donât forget: changing the look of the grid itself, like the image ratio or gaps, will give this a whole different feel!
Now itâs your turn. Tweak it, break it, rebuild it, and make something cool.
I really hope you enjoy this effect! Thanks for checking by đ