Skip to content

Instantly share code, notes, and snippets.

@moughamir
Created March 2, 2026 15:15
Show Gist options
  • Select an option

  • Save moughamir/1e3a9f2fde0aacd665438a3f937262fc to your computer and use it in GitHub Desktop.

Select an option

Save moughamir/1e3a9f2fde0aacd665438a3f937262fc to your computer and use it in GitHub Desktop.
Scroll Animation with Grid (Motion)
<body>
<div class="content-wrap">
<header>
<h1 class="fluid">let's<br />scroll.</h1>
<h2 class="fluid">Origionally from
<a href="https://codepen.io/jh3y/pen/VYZwOwd" target="_blank">Jhey →</a>, converted to Motion
</h2>
</header>
<main>
<section>
<div class="content">
<div class="grid">
<!-- Layer 1: Outer edges (6 images) -->
<div class="layer">
<div>
<img src="https://images.unsplash.com/photo-1463100099107-aa0980c362e6?w=800&auto=format&fit=crop&q=60&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxzZWFyY2h8MjZ8fGZhc2hpb258ZW58MHx8MHx8fDA%3D" alt="" />
</div>
<div>
<img src="https://images.unsplash.com/photo-1556304044-0699e31c6a34?w=800&auto=format&fit=crop&q=60&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxzZWFyY2h8OTJ8fGZhc2hpb258ZW58MHx8MHx8fDA%3D" alt="" />
</div>
<div>
<img src="https://images.unsplash.com/photo-1590330297626-d7aff25a0431?w=800&auto=format&fit=crop&q=60&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxzZWFyY2h8MTA3fHxmYXNoaW9ufGVufDB8fDB8fHww" alt="" />
</div>
<div>
<img src="https://images.unsplash.com/photo-1487222477894-8943e31ef7b2?w=800&auto=format&fit=crop&q=60&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxzZWFyY2h8MTk1fHxmYXNoaW9ufGVufDB8fDB8fHww" alt="" />
</div>
<div>
<img src="https://images.unsplash.com/photo-1488161628813-04466f872be2?w=800&auto=format&fit=crop&q=60&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxzZWFyY2h8NzR8fG1vZGVsJTIwZmFzaGlvbiUyMHN0cmVldHxlbnwwfHwwfHx8MA%3D%3D" alt="" />
</div>
<div>
<img src="https://images.unsplash.com/photo-1565321590372-09331b9dd1eb?w=800&auto=format&fit=crop&q=60&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxzZWFyY2h8MTR8fGFpciUyMGpvcmRhbnxlbnwwfHwwfHx8MA%3D%3D" alt="" />
</div>
</div>
<!-- Layer 2: Inner columns (6 images) -->
<div class="layer">
<div>
<img src="https://images.unsplash.com/photo-1531525645387-7f14be1bdbbd?w=800&auto=format&fit=crop&q=60&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxzZWFyY2h8MjM4fHxwcm9kdWN0fGVufDB8fDB8fHww" alt="" />
</div>
<div>
<img src="https://images.unsplash.com/photo-1637414165749-9b3cd88b8271?w=800&auto=format&fit=crop&q=60&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxzZWFyY2h8Mjd8fHRlY2glMjBwcm9kdWN0fGVufDB8fDB8fHww" alt="" />
</div>
<div>
<img src="https://images.unsplash.com/photo-1699911251220-8e0de3b5ce88?w=800&auto=format&fit=crop&q=60&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxzZWFyY2h8N3x8b25ld2hlZWx8ZW58MHx8MHx8fDA%3D" alt="" />
</div>
<div>
<img src="https://images.unsplash.com/photo-1667483629944-6414ad0648c5?w=800&auto=format&fit=crop&q=60&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxzZWFyY2h8MTl8fGx1eHVyeSUyMHdhdGNofGVufDB8fDB8fHww" alt="" />
</div>
<div>
<img src="https://plus.unsplash.com/premium_photo-1706078438060-d76ced26d8d5?w=800&auto=format&fit=crop&q=60&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxzZWFyY2h8MjF8fGNhbWVyYSUyMHBvbGFyb2lkfGVufDB8fDB8fHww" alt="" />
</div>
<div>
<img src="https://images.unsplash.com/photo-1525385444278-b7968e7e28dc?w=800&auto=format&fit=crop&q=60&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxzZWFyY2h8NDZ8fGl0ZW18ZW58MHx8MHx8fDA%3D" alt="" />
</div>
</div>
<!-- Layer 3: Center column top and bottom (2 images) -->
<div class="layer">
<div>
<img src="https://images.unsplash.com/reserve/LJIZlzHgQ7WPSh5KVTCB_Typewriter.jpg?w=800&auto=format&fit=crop&q=60&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxzZWFyY2h8Mnx8aXRlbXxlbnwwfHwwfHx8MA%3D%3D" alt="" />
</div>
<div>
<img src="https://images.unsplash.com/photo-1515886657613-9f3515b0c78f?w=800&auto=format&fit=crop&q=60&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxzZWFyY2h8Mnx8ZmFzaGlvbnxlbnwwfHwwfHx8MA%3D%3D" alt="" />
</div>
</div>
<!-- Center scaler image -->
<div class="scaler">
<img src="https://assets.codepen.io/605876/model-shades.jpg?format=auto&quality=100" alt="" />
</div>
</div>
</div>
</section>
<section>
<h2 class="fluid">fin.</h2>
</section>
</main>
</div>
</body>
</html>
import { animate, scroll, stagger, cubicBezier } from 'https://cdn.jsdelivr.net/npm/motion@11.11.16/+esm';
// Check for reduced motion preference
const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
if (prefersReducedMotion) {
// Skip animations if user prefers reduced motion
console.log('Reduced motion preference detected - skipping animations');
} else {
let image = document.querySelector('.scaler img');
let firstSection = document.querySelector('main section:first-of-type');
let layers = document.querySelectorAll('.grid > .layer');
// Measure the natural size before animating
const naturalWidth = image.offsetWidth;
const naturalHeight = image.offsetHeight;
// Get viewport dimensions in pixels
const viewportWidth = window.innerWidth;
const viewportHeight = window.innerHeight;
// Animate image on scroll - shrink from full screen to natural size
scroll(
animate(image, {
width: [viewportWidth, naturalWidth],
height: [viewportHeight, naturalHeight]
}, {
width: { easing: cubicBezier(0.65, 0, 0.35, 1) }, // GSAP power2.inOut
height: { easing: cubicBezier(0.42, 0, 0.58, 1) } // GSAP power1.inOut
}),
{
target: firstSection,
offset: ['start start', '80% end end']
}
);
// Animate each layer with staggered timing
// Different easing per layer: power1, power3, power4
const scaleEasings = [
cubicBezier(0.42, 0, 0.58, 1), // Layer 1: GSAP power1.inOut
cubicBezier(0.76, 0, 0.24, 1), // Layer 2: GSAP power3.inOut
cubicBezier(0.87, 0, 0.13, 1) // Layer 3: GSAP power4.inOut
];
layers.forEach((layer, index) => {
// Calculate different end points for each layer
const endOffset = `${1 - (index * 0.05)} end`;
// fade: opacity stays 0 until 55% of scroll progress, then fades to 1
scroll(
animate(layer, {
opacity: [0, 0, 1]
}, {
offset: [0, 0.55, 1], // Hold at 0 until 55%, then animate to 1
easing: cubicBezier(0.61, 1, 0.88, 1) // GSAP sine.out
}),
{
target: firstSection,
offset: ['start start', endOffset]
}
);
// reveal: scale stays 0 until 30% of scroll progress, then scales to 1
scroll(
animate(layer, {
scale: [0, 0, 1]
}, {
offset: [0, 0.3, 1], // Hold at 0 until 30%, then animate to 1
easing: scaleEasings[index] // Different power curve per layer
}),
{
target: firstSection,
offset: ['start start', endOffset]
}
);
});
}
/* Step 1: Basic Grid Setup - No animations yet */
*,
*::before,
*::after {
box-sizing: border-box;
}
:root {
--gutter: 2rem;
}
@media (max-width: 600px) {
:root {
--gutter: 1rem;
}
}
body {
margin: 0;
font-family: 'SF Pro Text', 'SF Pro Icons', 'Helvetica Neue', Helvetica, Arial, sans-serif, system-ui;
background: #000;
color: #fff;
}
.content-wrap {
background: #000;
overflow: clip;
}
/* Header */
header {
min-height: 100vh;
display: grid;
align-content: center;
max-width: calc(100% - (2 * var(--gutter)));
padding-left: 48px;
text-align: left;
}
h2 {
padding-top: 12px;
}
a {
color:white;
}
.fluid {
font-size: clamp(4rem, 12vw, 12rem);
line-height: 0.6;
margin: 0;
}
h2.fluid {
font-size: clamp(0.5rem, 2vw, 1.5rem);
padding-top: 48px;
}
/* Section setup - Sticky spacer trick */
main section:first-of-type {
min-height: 240vh; /* Extra height for scroll space */
}
.content {
min-height: 100vh;
width: 100vw;
display: flex;
place-items: center;
align-content: center;
position: sticky;
top: 0;
overflow: hidden;
}
main section:last-of-type {
min-height: 100vh;
display: grid;
place-items: center;
}
/* Grid Layout - 5 columns x 3 rows */
.grid {
--offset: 0;
--container-width: 1600px;
--gap: clamp(10px, 7.35vw, 80px);
width: var(--container-width);
max-width: calc(100% - (2 * var(--gutter)));
display: grid;
grid-template-columns: repeat(5, 1fr);
grid-template-rows: repeat(3, auto);
gap: var(--gap);
margin: 0 auto;
align-content: center;
position: absolute;
top: 50%;
left: 50%;
translate: -50% -50%;
}
@media (max-width: 600px) {
.grid {
grid-template-columns: repeat(3, 1fr);
--offset: -1;
}
.grid > .layer:nth-of-type(1) {
display: none;
}
}
/* Layers using subgrid */
.grid > .layer {
display: grid;
grid-column: 1 / -1;
grid-row: 1 / -1;
grid-template-columns: subgrid;
grid-template-rows: subgrid;
}
/* Layer 1: Outer edges - odd on left column, even on right column */
.grid > .layer:nth-of-type(1) div:nth-of-type(odd) {
grid-column: 1;
}
.grid > .layer:nth-of-type(1) div:nth-of-type(even) {
grid-column: -2;
}
/* Layer 2: Inner columns */
.grid > .layer:nth-of-type(2) div:nth-of-type(odd) {
grid-column: calc(2 + var(--offset));
}
.grid > .layer:nth-of-type(2) div:nth-of-type(even) {
grid-column: calc(-3 - var(--offset));
}
/* Layer 3: Center column top and bottom */
.grid > .layer:nth-of-type(3) div:first-of-type {
grid-column: calc(3 + var(--offset));
grid-row: 1;
}
.grid > .layer:nth-of-type(3) div:last-of-type {
grid-column: calc(3 + var(--offset));
grid-row: -1;
}
.grid img {
width: 100%;
aspect-ratio: 4 / 5;
object-fit: cover;
border-radius: 1rem;
}
.grid .scaler {
position: relative;
grid-area: 2 / calc(3 + var(--offset));
}
.scaler {
z-index: 2;
width: 100%;
height: 100%;
position: relative;
img {
position: absolute;
top: 50%;
left: 50%;
translate: -50% -50%;
object-fit: cover;
border-radius: 1rem;
width: 100%;
height: 100%;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment