GSAP - ScrollTrigger

by Hugo Vale Pereira

HTML

<!--Tutorial from: https://www.youtube.com/watch?v=Gy9IS0Y6dy0  Nov 2025
inspired from: https://www.wildyriftian.com/works
-->

<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.13.0/gsap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.13.0/ScrollTrigger.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.13.0/ScrollToPlugin.min.js"></script>
<!-- <nav>
  <p>Tutorial</p>
  <p>GSAP</p>
</nav> -->

<div class="panel">Panel 1</div>
<div class="panel">Panel 2</div>
<div class="panel">Panel 3</div>
<div class="panel">Panel 4</div>
<div class="panel">Panel 5</div>

CSS

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

body {
  font-family: "Arial", sans-serif;
}

.panel {
  width: 100vw;
  height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 3rem;
  font-weight: bold;
  color: white;
}

.panel:nth-of-type(1) {
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
.panel:nth-of-type(2) {
  background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
}
.panel:nth-of-type(3) {
  background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
}
.panel:nth-of-type(4) {
  background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%);
}
.panel:nth-of-type(5) {
  background: linear-gradient(135deg, #fa709a 0%, #fee140 100%);
}

JavaScript

// gsap.registerPlugin(ScrollTrigger);

const panels = gsap.utils.toArray(".panel");
let isUserInteracting = false;
let snapTimeout;

// Detect when user starts interacting
window.addEventListener(
  "wheel",
  () => {
    console.log("wheel event");
    isUserInteracting = true;
    clearTimeout(snapTimeout);
  },
  { passive: true },
);
window.addEventListener(
  "touchstart",
  () => {
    console.log("touchstart event");
    isUserInteracting = true;
    clearTimeout(snapTimeout);
  },
  { passive: true },
);
window.addEventListener("mousedown", () => {
  console.log("mousedown event");
  isUserInteracting = true;
  clearTimeout(snapTimeout);
});

// Detect when user stops interacting
// window.addEventListener("wheel", handleInteractionEnd, { passive: true });
// window.addEventListener("touchend", handleInteractionEnd);
// window.addEventListener("mouseup", handleInteractionEnd);
window.addEventListener("scrollend", handleInteractionEnd);

//Polyfill for if the Browser doesn't support 'scrollend' event (Safari)
/* if ('onscrollend' in window) {
  document.onscrollend = callback
} else {
  document.onscroll = event => {
    clearTimeout(window.scrollEndTimer)
    window.scrollEndTimer = setTimeout(callback, 100)
  }
} */

function handleInteractionEnd() {
  console.log("scrollend event");
  clearTimeout(snapTimeout);
  snapTimeout = setTimeout(() => {
    isUserInteracting = false;
    snapToNearest();
  }, 50); // Small delay to detect end of interaction
}

function snapToNearest() {
  const scrollProgress =
    window.scrollY /
    (document.documentElement.scrollHeight - window.innerHeight);
  const targetPanel = Math.round(scrollProgress * (panels.length - 1));
  const targetScroll =
    (targetPanel / (panels.length - 1)) *
    (document.documentElement.scrollHeight - window.innerHeight);

  gsap.to(window, {
    scrollTo: targetScroll,
    duration: 0.5,
    ease: "power2.inOut",
 ...