Web Animations Medium
Horizontal Pinned Gallery
Pinned section with horizontal gallery movement tied to scroll progress.
Open in Lab
MCP
gsap scrolltrigger parallax
Targets: JS HTML
Code
body {
margin: 0;
font-family: "Avenir Next", sans-serif;
background: #070c15;
color: #f2f7ff;
overflow-x: hidden;
}
.back {
position: fixed;
top: 14px;
left: 14px;
color: #86e8ff;
text-decoration: none;
z-index: 20;
}
.intro,
.outro {
min-height: 70vh;
display: grid;
place-content: center;
text-align: center;
}
.pin-wrap {
height: 100vh;
overflow: hidden;
}
.track {
height: 100%;
display: flex;
gap: 1rem;
align-items: center;
padding: 0 6vw;
}
.card {
min-width: min(70vw, 620px);
height: 70vh;
border-radius: 18px;
display: grid;
place-content: center;
font-size: clamp(1.8rem, 5vw, 3rem);
background: linear-gradient(135deg, #203451, #6f3db1);
border: 1px solid #3c4f71;
}
body.no-motion .pin-wrap {
height: auto;
overflow: visible;
padding: 1rem 0 2rem;
}
body.no-motion .track {
height: auto;
flex-direction: column;
align-items: stretch;
padding: 0 4vw;
}
body.no-motion .card {
min-width: 100%;
width: 100%;
height: 44vh;
}if (!window.MotionPreference) {
const __mql = window.matchMedia("(prefers-reduced-motion: reduce)");
const __listeners = new Set();
const MotionPreference = {
prefersReducedMotion() {
return __mql.matches;
},
setOverride(value) {
const reduced = Boolean(value);
document.documentElement.classList.toggle("reduced-motion", reduced);
window.dispatchEvent(new CustomEvent("motion-preference", { detail: { reduced } }));
for (const listener of __listeners) {
try {
listener({ reduced, override: reduced, systemReduced: __mql.matches });
} catch {}
}
},
onChange(listener) {
__listeners.add(listener);
try {
listener({
reduced: __mql.matches,
override: null,
systemReduced: __mql.matches,
});
} catch {}
return () => __listeners.delete(listener);
},
getState() {
return { reduced: __mql.matches, override: null, systemReduced: __mql.matches };
},
};
window.MotionPreference = MotionPreference;
}
const reduced = window.MotionPreference.prefersReducedMotion();
if (!window.gsap || !window.ScrollTrigger || reduced) {
document.body.classList.add("no-motion");
} else {
window.gsap.registerPlugin(window.ScrollTrigger);
const track = document.getElementById("track");
window.gsap.to(track, {
x: () => -Math.max(0, track.scrollWidth - window.innerWidth),
ease: "none",
scrollTrigger: {
trigger: ".pin-wrap",
pin: true,
scrub: 1,
start: "top top",
end: () =>
`+=${Math.max(1, track.scrollWidth - window.innerWidth + window.innerHeight * 0.9)}`,
invalidateOnRefresh: true,
},
});
window.addEventListener("resize", () => {
window.ScrollTrigger.refresh();
});
}<!doctype html>
<html lang="en"><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Demo 04 - Horizontal Pinned Gallery</title><link rel="stylesheet" href="style.css" /></head>
<body>
<a class="back" href="../">Back</a>
<section class="intro"><h1>Horizontal Pinned Gallery</h1><p>Scroll down to move sideways.</p></section>
<section class="pin-wrap"><div class="track" id="track">
<article class="card">Layer 01</article>
<article class="card">Layer 02</article>
<article class="card">Layer 03</article>
<article class="card">Layer 04</article>
<article class="card">Layer 05</article>
</div></section>
<section class="outro"><p>End section</p></section>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.12.5/dist/gsap.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.12.5/dist/ScrollTrigger.min.js"></script>
<script src="script.js"></script>
</body></html>Horizontal Pinned Gallery
Pinned section with horizontal gallery movement tied to scroll progress.
Source
- Repository:
libs-gen - Original demo id:
04-horizontal-pinned-gallery
Notes
Pinned section with horizontal gallery movement tied to scroll progress.