Skip to content

Instantly share code, notes, and snippets.

@aldoyh
Last active August 14, 2025 06:24
Show Gist options
  • Select an option

  • Save aldoyh/74d26ec7663a9ec0ef44d84716cc5480 to your computer and use it in GitHub Desktop.

Select an option

Save aldoyh/74d26ec7663a9ec0ef44d84716cc5480 to your computer and use it in GitHub Desktop.
Swiped Scrolled Website Design v2.0
// Ensure this file is loaded after the libraries in your HTML (gsap, observer, split-type)
document.addEventListener("DOMContentLoaded", () => {
gsap.registerPlugin(Observer);
const sections = gsap.utils.toArray(".slide");
const headings = gsap.utils.toArray(".slide__heading");
const countEl = document.querySelector(".count");
let animating = false;
let currentIndex = 0;
const wrap = gsap.utils.wrap(0, sections.length);
// Split headings into characters for animation
const splitHeadings = headings.map((heading) => {
return new SplitType(heading, {
types: "chars",
charClass: "char"
});
});
// Set initial states for all elements
gsap.set(".slide:not(:first-child)", {
visibility: "hidden"
});
gsap.set(".char", {
yPercent: 120,
rotationZ: 15
});
// Set initial state for the first slide's characters
if (splitHeadings.length > 0) {
gsap.set(splitHeadings[0].chars, { yPercent: 0, rotationZ: 0 });
}
function animateCounter(newIndex) {
const tl = gsap.timeline();
tl
.to(countEl, { y: -20, opacity: 0, duration: 0.3, ease: "power2.in" })
.set(countEl, { innerText: newIndex + 1 })
.fromTo(
countEl,
{ y: 20, opacity: 0 },
{ y: 0, opacity: 1, duration: 0.3, ease: "power2.out" }
);
}
function gotoSection(index, direction) {
if (animating) return;
animating = true;
index = wrap(index);
const currentSection = sections[currentIndex];
const nextSection = sections[index];
const currentChars = splitHeadings[currentIndex].chars;
const nextChars = splitHeadings[index].chars;
const currentImage = currentSection.querySelector(".slide__img");
const nextImage = nextSection.querySelector(".slide__img");
const nextColor = nextSection.dataset.color;
const tl = gsap.timeline({
defaults: { duration: 1.2, ease: "power4.inOut" },
onStart: () => {
gsap.set(nextSection, { visibility: "visible" });
},
onComplete: () => {
gsap.set(currentSection, { visibility: "hidden" });
currentIndex = index;
animating = false;
}
});
tl
.to("body", { backgroundColor: nextColor }, 0)
// Animate outgoing elements
.to(
currentChars,
{ yPercent: -120, rotationZ: -15, stagger: 0.03, duration: 1 },
0
)
.to(
currentImage,
{
xPercent: direction * 50,
scale: 1.2,
skewX: direction * 15,
filter: "blur(10px)",
duration: 1.5
},
0
)
.to(
currentSection.querySelector(".slide__inner"),
{
clipPath:
direction === 1
? "polygon(0% 0%, 100% 0%, 100% 0%, 0% 0%)"
: "polygon(0% 100%, 100% 100%, 100% 100%, 0% 100%)",
duration: 1.5
},
0
)
// Animate incoming elements
.fromTo(
nextSection.querySelector(".slide__inner"),
{
clipPath:
direction === 1
? "polygon(0% 100%, 100% 100%, 100% 100%, 0% 100%)"
: "polygon(0% 0%, 100% 0%, 100% 0%, 0% 0%)"
},
{
clipPath: "polygon(0% 0%, 100% 0%, 100% 100%, 0% 100%)",
duration: 1.5
},
0
)
.from(
nextImage,
{
xPercent: -direction * 50,
scale: 1.2,
skewX: -direction * 15,
filter: "blur(10px)",
duration: 1.5
},
0
)
.to(
nextChars,
{ yPercent: 0, rotationZ: 0, stagger: 0.03, duration: 1 },
0.2
);
animateCounter(index);
}
Observer.create({
type: "wheel,touch,pointer",
wheelSpeed: -1, // Invert wheel direction for natural scrolling
onUp: () => gotoSection(currentIndex + 1, 1),
onDown: () => gotoSection(currentIndex - 1, -1),
tolerance: 10,
preventDefault: true
});
document.addEventListener("keydown", (e) => {
if (e.key === "ArrowUp" || e.key === "ArrowLeft") {
gotoSection(currentIndex - 1, -1);
}
if (e.key === "ArrowDown" || e.key === "ArrowRight" || e.key === " ") {
gotoSection(currentIndex + 1, 1);
}
});
});
<script src="https://assets.codepen.io/16327/gsap-latest-beta.min.js"></script>
<script src="https://assets.codepen.io/16327/ScrollTrigger.min.js"></script>
<script src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/16327/SplitText3.min.js"></script>
<script src="https://unpkg.com/gsap@3/dist/TextPlugin.min.js"></script>
@font-face {
font-family: "Bandeins Sans & Strange Variable";
src: url("https://res.cloudinary.com/dldmpwpcp/raw/upload/v1566406079/BandeinsStrangeVariable_esetvq.ttf");
}
@import url("https://fonts.googleapis.com/css2?family=Sora:wght@400;700&display=swap");
:root {
--bg-color: #4361ee;
--text-color: #f2f1fc;
}
* {
box-sizing: border-box;
user-select: none;
}
body {
color: var(--text-color);
background-color: var(--bg-color);
font-family: "Sora", sans-serif;
height: 100vh;
width: 100vw;
overflow: hidden;
margin: 0;
-webkit-font-smoothing: antialiased;
}
body::before {
content: "";
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAMAAAAp4XiDAAAAUVBMVEWFhYWDg4N3d3dtbW17e3t1dXWBgYGHh4d5eXlzc3OLi4ubm5uVlZWPj4+NjY19fX2JiYl/f39ra2uRkZGZmZlpaWmXl5dvb29xcXGTk5NnZ2c8TV1mAAAACnRSTlMAzDPKzsozvHBeC8/1amIAAAAZSURBVDjLY2AYwYAQhTBwsgAUVxgxMHYwAgAAVQYAgB9gKRAAAAAASUVORK5CYII=");
opacity: 0.04;
z-index: 1000;
pointer-events: none;
}
figure {
margin: 0;
overflow: hidden;
}
footer {
position: fixed;
z-index: 999;
bottom: 0;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 2rem;
width: 100%;
height: 7em;
font-size: clamp(1rem, 2vw, 1.2rem);
}
a {
color: var(--text-color);
text-decoration: none;
font-weight: 700;
}
.ui {
position: fixed;
z-index: 10;
top: 0;
right: 0;
padding: 2rem;
text-align: right;
}
.ui__counter {
font-size: clamp(3rem, 4vw, 5rem);
border-bottom: 5px solid var(--text-color);
font-weight: 700;
line-height: 1;
overflow: hidden;
}
.ui__counter .count {
display: inline-block;
}
.slide {
height: 100vh;
width: 100vw;
top: 0;
left: 0;
position: fixed;
visibility: hidden;
}
.slide__outer,
.slide__inner {
width: 100%;
height: 100%;
overflow: hidden;
}
.slide__inner {
clip-path: polygon(0% 100%, 100% 100%, 100% 100%, 0% 100%);
}
.slide__content {
display: flex;
align-items: center;
justify-content: center;
position: absolute;
height: 100%;
width: 100%;
top: 0;
background-color: var(--slide-bg);
}
.slide__container {
position: relative;
width: 100%;
max-width: 1400px;
height: 100%;
margin: 0 auto;
display: grid;
grid-template-columns: repeat(12, 1fr);
grid-template-rows: repeat(12, 1fr);
padding: 2rem;
}
.slide__heading {
grid-area: 4 / 2 / 6 / 12;
font-family: "Bandeins Sans & Strange Variable";
font-size: clamp(5rem, 15vw, 15rem);
font-weight: 900;
font-variation-settings: "wdth" 200;
margin: 0;
padding: 0;
color: var(--text-color);
z-index: 3;
line-height: 0.9;
mix-blend-mode: difference;
}
.slide__heading .char {
display: inline-block;
will-change: transform, opacity;
}
.slide__img-cont {
grid-area: 5 / 6 / 11 / 11;
overflow: hidden;
z-index: 2;
will-change: transform;
}
.slide__img-cont img {
width: 100%;
height: 100%;
object-fit: cover;
will-change: transform, filter;
}
.slide:first-child {
visibility: visible;
}
.slide:first-child .slide__inner {
clip-path: polygon(0% 0%, 100% 0%, 100% 100%, 0% 100%);
}
@media screen and (max-width: 900px) {
.slide__heading {
grid-area: 3 / 2 / 5 / 12;
}
.slide__img-cont {
grid-area: 4 / 2 / 10 / 12;
}
footer,
.ui {
padding: 1.5rem;
}
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>GSAP Kinetic Morph Scroll</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="ui">
<div class="ui__counter">0<span class="count">1</span></div>
</div>
<section class="slide" data-color="#6d597a">
<div class="slide__outer">
<div class="slide__inner">
<div class="slide__content">
<div class="slide__container">
<h2 class="slide__heading">Kinetic</h2>
<figure class="slide__img-cont">
<img class="slide__img" src='https://images.unsplash.com/photo-1567016376408-0226e4d0c1ea?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=800' alt='Minimalist chair'>
</figure>
</div>
</div>
</div>
</div>
</section>
<section class="slide" data-color="#355070">
<div class="slide__outer">
<div class="slide__inner">
<div class="slide__content">
<div class="slide__container">
<h2 class="slide__heading">Morph</h2>
<figure class="slide__img-cont">
<img class="slide__img" src='https://images.unsplash.com/photo-1558603668-6570496b66f8?ixlib=rb-1.2.1&q=85&fm=jpg&crop=entropy&cs=srgb&w=800' alt='Interior with wooden chair'>
</figure>
</div>
</div>
</div>
</div>
</section>
<section class="slide" data-color="#b56576">
<div class="slide__outer">
<div class="slide__inner">
<div class="slide__content">
<div class="slide__container">
<h2 class="slide__heading">Fluid</h2>
<figure class="slide__img-cont">
<img class="slide__img" src='https://images.unsplash.com/photo-1537165924986-cc3568f5d454?ixlib=rb-1.2.1&q=85&fm=jpg&crop=entropy&cs=srgb&w=800' alt='Pink velvet armchair'>
</figure>
</div>
</div>
</div>
</div>
</section>
<section class="slide" data-color="#9a8c98">
<div class="slide__outer">
<div class="slide__inner">
<div class="slide__content">
<div class="slide__container">
<h2 class="slide__heading">Scroll</h2>
<figure class="slide__img-cont">
<img class="slide__img" src='https://images.unsplash.com/photo-1589271243958-d61e12b61b97?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=800' alt='Modern white chair'>
</figure>
</div>
</div>
</div>
</div>
</section>
<footer>
<a href="https://gsap.com/">GSAP</a>
<p>Enhanced Demo</p>
</footer>
<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/Observer.min.js"></script>
<script src="https://unpkg.com/split-type"></script>
<script src="script.js"></script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment