Belajar ScrollTrigger GSAP: Membuat Animasi Scroll

Pernahkah Anda mengunjungi sebuah website dan melihat elemen-elemen beranimasi saat Anda melakukan scroll? Gambar fade in, teks slide masuk dari samping, atau background yang bergerak lebih lambat dari content? Semua itu adalah hasil dari ScrollTrigger, sebuah plugin powerful dari GSAP yang memungkinkan Anda membuat animasi yang terikat dengan gerakan scroll user.

ScrollTrigger adalah fitur yang paling banyak dicari dan paling sering digunakan dari GSAP modern. Tidak hanya gratis (sejak GSAP 3.0), tetapi juga sangat mudah digunakan dan kompatibel dengan hampir semua browser modern. Dengan ScrollTrigger, Anda bisa membuat pengalaman web yang benar-benar interaktif dan memorable.

Di artikel ini, kita akan explore berbagai teknik ScrollTrigger, dari yang basic seperti fade-in on scroll, hingga yang lebih advanced seperti parallax, pinning, dan scroll-driven animations. Setiap teknik akan disertai dengan file HTML terpisah yang bisa langsung Anda praktikkan.

Mari kita mulai!


Apa itu ScrollTrigger?

ScrollTrigger adalah sebuah plugin dari GSAP yang memungkinkan Anda untuk "trigger" (memicu) animasi berdasarkan posisi scroll user. Dengan ScrollTrigger, Anda bisa:

  1. Memicu animasi saat elemen masuk viewport (area yang terlihat di layar)
  2. Membuat animasi yang berjalan seiring pergerakan scroll (scrubbing)
  3. "Menjepit" elemen di layar saat user scroll (pinning)
  4. Membuat parallax effects di mana elemen bergerak pada kecepatan berbeda
  5. Membuat scroll-driven animations yang kompleks dan sinematik

Mengapa ScrollTrigger Penting?

  1. Meningkatkan User Engagement: Animasi pada scroll membuat user lebih engaged dengan content.
  2. Menceritakan Story: Animasi bisa digunakan untuk guide user melalui narrative atau flow tertentu.
  3. Meningkatkan Perceived Performance: Animasi membuat loading atau tunggu terasa lebih cepat.
  4. Creating "Wow" Moment: Animasi scroll yang baik membuat user memorable dengan website Anda.
  5. Mobile-Friendly: ScrollTrigger bekerja sempurna di mobile dengan touch scroll.

Setup Dasar ScrollTrigger

Untuk menggunakan ScrollTrigger, Anda perlu memuat library GSAP dan ScrollTrigger plugin:

*<!-- Load GSAP -->*
<script src="<https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js>"></script>

*<!-- Load ScrollTrigger Plugin -->*
<script src="<https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/ScrollTrigger.min.js>"></script>

<script>
    // Daftarkan ScrollTrigger plugin
    gsap.registerPlugin(ScrollTrigger);
</script>

Konsep Fundamental ScrollTrigger

Sebelum kita ke berbagai teknik, penting memahami beberapa properti fundamental dari ScrollTrigger:

1. trigger: Elemen mana yang akan "memicu" animasi saat masuk viewport.

ScrollTrigger: {
    trigger: "#section1" *// Animasi dimulai saat #section1 masuk viewport*
}

2. start dan end: Di mana animasi dimulai dan berakhir.

start: "top center"   *// Mulai saat top elemen mencapai center layar*
end: "bottom center"  *// Berakhir saat bottom elemen mencapai center layar*

3. markers: Untuk debugging (menampilkan garis merah untuk melihat start/end points).

markers: true *// Aktifkan untuk development, nonaktifkan saat production*

4. scrub: Mengikat animasi ke pergerakan scroll (nilai 0-1 atau true/false).

scrub: 1 *// Smooth 1-second lag, scrub: true untuk instant*

5. pin: Menjepit elemen di posisi saat user scroll.

pin: true *// Jepit elemen ini saat scrolling*

Teknik 1: Fade In on Scroll

Teknik paling dasar dan paling populer: membuat elemen fade in (muncul) saat scroll mencapainya.

1. FadeIn.gif
Fade In on Scroll

fade-on-scroll.html

<!DOCTYPE html>
<html lang="id">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>1. ScrollTrigger - Fade In on Scroll</title>
    <style>
      * {
        margin: 0;
        padding: 0;
        box-sizing: border-box;
      }

      body {
        font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
        background-color: #f0f2f5;
        line-height: 1.6;
      }

      .container {
        max-width: 1000px;
        margin: 0 auto;
        padding: 40px 20px;
      }

      h1 {
        text-align: center;
        color: #1a1a2e;
        font-size: 3rem;
        margin-bottom: 20px;
      }

      .description {
        background: white;
        padding: 30px;
        border-radius: 15px;
        margin-bottom: 60px;
        box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
        color: #333;
        font-size: 1.1rem;
      }

      .spacer {
        height: 400px;
        display: flex;
        align-items: center;
        justify-content: center;
        background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
        color: white;
        font-size: 1.5rem;
        font-weight: bold;
        border-radius: 15px;
        margin-bottom: 60px;
        box-shadow: 0 20px 50px rgba(0, 0, 0, 0.2);
      }

      .fade-box {
        background: white;
        padding: 50px;
        border-radius: 15px;
        margin-bottom: 60px;
        box-shadow: 0 20px 50px rgba(0, 0, 0, 0.1);
        text-align: center;
      }

      .fade-box h2 {
        color: #667eea;
        font-size: 2.5rem;
        margin-bottom: 20px;
      }

      .fade-box p {
        color: #666;
        font-size: 1.2rem;
        line-height: 1.8;
      }

      .gsap-marker-scroller-start,
      .gsap-marker-scroller-end,
      .gsap-marker-start,
      .gsap-marker-end {
        display: none; /* Matikan markers di production */
      }
    </style>
  </head>
  <body>
    <div class="container">
      <h1>Teknik 1: Fade In on Scroll</h1>

      <div class="description">
        <h3>Cara Kerja:</h3>
        <p>
          Teknik paling dasar dari ScrollTrigger. Elemen akan fade in (muncul
          secara bertahap) saat Anda scroll dan elemen masuk ke dalam viewport
          (area yang terlihat).
        </p>
        <p><strong>Scroll ke bawah untuk melihat animasi!</strong></p>
      </div>

      <!-- Spacer untuk scroll distance -->
      <div class="spacer">Scroll ke bawah...</div>

      <!-- Box 1: Fade In -->
      <div class="fade-box" id="box1">
        <h2>📦 Box 1</h2>
        <p>
          Elemen ini fade in saat Anda scroll mencapainya. Lihat bagaimana
          opacity berubah dari 0 menjadi 1.
        </p>
      </div>

      <!-- More spacer -->
      <div class="spacer">Lanjutkan scroll...</div>

      <!-- Box 2: Fade In dengan delay -->
      <div class="fade-box" id="box2">
        <h2>📦 Box 2</h2>
        <p>
          Elemen ini juga fade in, tapi dengan durasi yang lebih lama,
          menciptakan efek yang lebih smooth.
        </p>
      </div>

      <!-- More spacer -->
      <div class="spacer">Terus scroll...</div>

      <!-- Box 3: Fade In dengan Y movement -->
      <div class="fade-box" id="box3">
        <h2>📦 Box 3</h2>
        <p>
          Elemen ini fade in sambil bergerak naik dari bawah. Kombinasi opacity
          dan y-position.
        </p>
      </div>

      <!-- More spacer -->
      <div class="spacer">Scroll satu kali lagi...</div>

      <!-- Box 4: Multiple properties -->
      <div class="fade-box" id="box4">
        <h2>📦 Box 4</h2>
        <p>
          Elemen ini fade in sambil scale up (membesar). Kombinasi opacity dan
          scale property.
        </p>
      </div>

      <div
        style="
          height: 400px;
          display: flex;
          align-items: center;
          justify-content: center;
          color: #999;
          font-size: 1.2rem;
        "
      >
        Akhir dari halaman! 🎉
      </div>
    </div>

    <!-- Load GSAP dan ScrollTrigger dari CDN -->
    <script src="<https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js>"></script>
    <script src="<https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/ScrollTrigger.min.js>"></script>

    <script>
      // Daftarkan ScrollTrigger plugin
      gsap.registerPlugin(ScrollTrigger);

      // ===== ANIMASI 1: Basic Fade In =====
      gsap.from("#box1", {
        opacity: 0,
        duration: 1,
        scrollTrigger: {
          trigger: "#box1", // Elemen yang akan trigger animasi
          start: "top 80%", // Mulai saat top box mencapai 80% dari layar
          end: "top 20%", // Berakhir saat top box mencapai 20% dari layar
          markers: false, // Set true untuk melihat garis debugging
          scrub: false, // Tidak di-scrub, animasi independent
          toggleActions: "play reverse play reverse", // TAMBAHKAN BARIS INI
        },
      });

      // ===== ANIMASI 2: Fade In dengan durasi lebih lama =====
      gsap.from("#box2", {
        opacity: 0,
        duration: 1.5, // Durasi lebih lama
        scrollTrigger: {
          trigger: "#box2",
          start: "top 80%",
          end: "top 20%",
          markers: false,
          toggleActions: "play reverse play reverse", // TAMBAHKAN BARIS INI
        },
      });

      // ===== ANIMASI 3: Fade In dengan Y movement =====
      gsap.from("#box3", {
        opacity: 0,
        y: 100, // Mulai dari 100px di bawah
        duration: 1,
        scrollTrigger: {
          trigger: "#box3",
          start: "top 80%",
          end: "top 20%",
          markers: false,
          toggleActions: "play reverse play reverse", // TAMBAHKAN BARIS INI
        },
      });

      // ===== ANIMASI 4: Fade In dengan Scale =====
      gsap.from("#box4", {
        opacity: 0,
        scale: 0.5, // Mulai dari 50% ukuran
        duration: 1.2,
        scrollTrigger: {
          trigger: "#box4",
          start: "top 80%",
          end: "top 20%",
          markers: false,
          toggleActions: "play reverse play reverse", // TAMBAHKAN BARIS INI
        },
      });

      // Refresh ScrollTrigger saat halaman selesai load
      window.addEventListener("load", () => ScrollTrigger.refresh());
    </script>
  </body>
</html>

Teknik 2: Slide In from Left/Right

Membuat elemen bergeser masuk dari samping saat scroll.

2. SlideIn.gif
Slide In from Left/Right

slide-on-scroll.html

<!DOCTYPE html>
<html lang="id">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>2. ScrollTrigger - Slide In on Scroll</title>
    <style>
      * {
        margin: 0;
        padding: 0;
        box-sizing: border-box;
      }

      body {
        font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
        background-color: #f0f2f5;
        line-height: 1.6;
      }

      .container {
        max-width: 1000px;
        margin: 0 auto;
        padding: 40px 20px;
      }

      h1 {
        text-align: center;
        color: #1a1a2e;
        font-size: 3rem;
        margin-bottom: 20px;
      }

      .description {
        background: white;
        padding: 30px;
        border-radius: 15px;
        margin-bottom: 60px;
        box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
        color: #333;
        font-size: 1.1rem;
      }

      .spacer {
        height: 400px;
        display: flex;
        align-items: center;
        justify-content: center;
        background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
        color: white;
        font-size: 1.5rem;
        font-weight: bold;
        border-radius: 15px;
        margin-bottom: 60px;
        box-shadow: 0 20px 50px rgba(0, 0, 0, 0.2);
      }

      .slide-box {
        background: white;
        padding: 50px;
        border-radius: 15px;
        margin-bottom: 60px;
        box-shadow: 0 20px 50px rgba(0, 0, 0, 0.1);
      }

      .slide-box h2 {
        color: #667eea;
        font-size: 2.5rem;
        margin-bottom: 20px;
      }

      .slide-box p {
        color: #666;
        font-size: 1.2rem;
        line-height: 1.8;
      }

      /* Grid layout untuk multiple boxes */
      .slide-grid {
        display: grid;
        grid-template-columns: 1fr 1fr;
        gap: 30px;
        margin-bottom: 60px;
      }

      .slide-item {
        background: white;
        padding: 40px;
        border-radius: 15px;
        box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
        text-align: center;
      }

      .slide-item h3 {
        color: #667eea;
        font-size: 1.8rem;
        margin-bottom: 15px;
      }

      @media (max-width: 768px) {
        .slide-grid {
          grid-template-columns: 1fr;
        }
      }
    </style>
  </head>
  <body>
    <div class="container">
      <h1>Teknik 2: Slide In on Scroll</h1>

      <div class="description">
        <h3>Cara Kerja:</h3>
        <p>
          Teknik ini membuat elemen bergeser masuk dari samping (kiri atau
          kanan) saat Anda scroll. Kombinasi dari X-position dan opacity membuat
          efek yang sangat smooth dan engaging.
        </p>
        <p>
          <strong
            >Scroll ke bawah untuk melihat animasi slide in dari berbagai
            arah!</strong
          >
        </p>
      </div>

      <div class="spacer">Scroll ke bawah...</div>

      <!-- Slide from Left -->
      <div class="slide-box" id="slideLeft">
        <h2>📦 Slide from Left</h2>
        <p>
          Elemen ini slide masuk dari sebelah kiri. Property X digunakan untuk
          menggerakkan elemen secara horizontal.
        </p>
      </div>

      <div class="spacer">Lanjutkan scroll...</div>

      <!-- Slide from Right -->
      <div class="slide-box" id="slideRight">
        <h2>📦 Slide from Right</h2>
        <p>
          Elemen ini slide masuk dari sebelah kanan. Nilai X negatif untuk slide
          dari kanan, positif untuk slide dari kiri.
        </p>
      </div>

      <div class="spacer">Terus scroll...</div>

      <!-- Slide from Top & Bottom Grid -->
      <div class="slide-grid">
        <div class="slide-item" id="slideTop">
          <h3>↑ Slide from Top</h3>
          <p>Slide masuk dari atas menggunakan property Y negatif.</p>
        </div>
        <div class="slide-item" id="slideBottom">
          <h3>↓ Slide from Bottom</h3>
          <p>Slide masuk dari bawah menggunakan property Y positif.</p>
        </div>
      </div>

      <div class="spacer">Scroll satu kali lagi...</div>

      <!-- Slide Diagonal -->
      <div class="slide-box" id="slideDiagonal">
        <h2>↗️ Slide Diagonal</h2>
        <p>
          Elemen ini slide masuk dari diagonal (kombinasi X dan Y). Efek yang
          sangat dynamic dan eye-catching!
        </p>
      </div>

      <div
        style="
          height: 400px;
          display: flex;
          align-items: center;
          justify-content: center;
          color: #999;
          font-size: 1.2rem;
        "
      >
        Akhir dari halaman! 🎉
      </div>
    </div>

    <!-- Load GSAP dan ScrollTrigger dari CDN -->
    <script src="<https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js>"></script>
    <script src="<https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/ScrollTrigger.min.js>"></script>

    <script>
      gsap.registerPlugin(ScrollTrigger);

      // ===== ANIMASI 1: Slide from Left =====
      gsap.from("#slideLeft", {
        x: -300, // Mulai 300px di sebelah kiri
        opacity: 0, // Mulai transparan
        duration: 1,
        scrollTrigger: {
          trigger: "#slideLeft",
          start: "top 80%",
          end: "top 20%",
          markers: false,
          toggleActions: "play reverse play reverse", // Agar bisa digunakan ulang
        },
      });
      // PENJELASAN: x: -300 berarti elemen mulai 300px ke kiri dari posisi normalnya.
      // Saat animasi, x akan berubah dari -300 menjadi 0 (posisi normal).

      // ===== ANIMASI 2: Slide from Right =====
      gsap.from("#slideRight", {
        x: 300, // Mulai 300px di sebelah kanan (positif)
        opacity: 0,
        duration: 1,
        scrollTrigger: {
          trigger: "#slideRight",
          start: "top 80%",
          end: "top 20%",
          markers: false,
          toggleActions: "play reverse play reverse", // Agar bisa digunakan ulang
        },
      });
      // PENJELASAN: x: 300 (positif) berarti elemen mulai 300px ke kanan.
      // Berbeda dengan yang sebelumnya (negatif untuk kiri).

      // ===== ANIMASI 3: Slide from Top =====
      gsap.from("#slideTop", {
        y: -200, // Mulai 200px di atas
        opacity: 0,
        duration: 1,
        scrollTrigger: {
          trigger: "#slideTop",
          start: "top 80%",
          end: "top 20%",
          markers: false,
          toggleActions: "play reverse play reverse", // Agar bisa digunakan ulang
        },
      });
      // PENJELASAN: y: -200 berarti elemen mulai 200px ke atas (negatif = atas).
      // Y positif akan menggerakkan elemen ke bawah.

      // ===== ANIMASI 4: Slide from Bottom =====
      gsap.from("#slideBottom", {
        y: 200, // Mulai 200px di bawah (positif)
        opacity: 0,
        duration: 1,
        scrollTrigger: {
          trigger: "#slideBottom",
          start: "top 80%",
          end: "top 20%",
          markers: false,
          toggleActions: "play reverse play reverse", // Agar bisa digunakan ulang
        },
      });
      // PENJELASAN: y: 200 (positif) berarti elemen mulai 200px ke bawah.

      // ===== ANIMASI 5: Slide Diagonal =====
      gsap.from("#slideDiagonal", {
        x: -400, // 400px ke kiri
        y: 200, // 200px ke bawah
        opacity: 0,
        scale: 0.8, // Bonus: juga scale dari 80%
        duration: 1.2,
        ease: "power2.out",
        scrollTrigger: {
          trigger: "#slideDiagonal",
          start: "top 80%",
          end: "top 20%",
          markers: false,
          toggleActions: "play reverse play reverse", // Agar bisa digunakan ulang
        },
      });
      // PENJELASAN: Kombinasi x dan y membuat elemen bergerak diagonal.
      // x: -400 = ke kiri, y: 200 = ke bawah, jadi diagonal dari kiri-bawah masuk.
      // ease: "power2.out" membuat gerakan terasa natural (cepat di awal, melambat di akhir).

      window.addEventListener("load", () => ScrollTrigger.refresh());
    </script>
  </body>
</html>

Penjelasan Kode:

  1. x property: Menggerakkan elemen secara horizontal
    • x: -300 = ke kiri
    • x: 300 = ke kanan
    • x: 0 = posisi normal
  2. y property: Menggerakkan elemen secara vertikal
    • y: -200 = ke atas
    • y: 200 = ke bawah
  3. gsap.from(): Animasi dimulai DARI state yang didefinisikan, menuju state normal
    • Di sini, elemen mulai dari posisi di luar viewport, lalu bergerak masuk
  4. scrollTrigger: Object yang mengatur kapan animasi dimulai
    • trigger: Elemen mana yang akan trigger animasi
    • start: "top 80%": Mulai saat top elemen mencapai 80% dari layar
    • end: "top 20%": Berakhir saat top elemen mencapai 20% dari layar

Teknik 3: Parallax Scrolling

Membuat elemen bergerak pada kecepatan berbeda, menciptakan efek kedalaman 3D yang menarik.

3. Parallax Scroll.gif
Parallax Scrolling

parallax-scroll.html

<!DOCTYPE html>
<html lang="id">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>3. ScrollTrigger - Pro Parallax</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        body {
            font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
            background-color: #0f0f0f;
            color: #ffffff;
            overflow-x: hidden;
        }

        /* --- HERO SECTION --- */
        .hero-section {
            height: 100vh;
            position: relative;
            overflow: hidden;
            display: flex;
            align-items: center;
            justify-content: center;
        }

        /* Background Image yang akan di-Parallax */
        .hero-bg {
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 130%; /* Lebih tinggi supaya bisa bergerak vertikal */
            background-image: url('<https://images.unsplash.com/photo-1451187580459-43490279c0fa?ixlib=rb-4.0.3&auto=format&fit=crop&w=1920&q=80>'); /* Gambar Space/Galaxy */
            background-size: cover;
            background-position: center;
            z-index: 1;
        }

        /* Overlay Gelap supaya teks terbaca */
        .hero-overlay {
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background: linear-gradient(to bottom, rgba(0,0,0,0.3), #0f0f0f);
            z-index: 2;
        }

        .hero-content {
            position: relative;
            z-index: 3;
            text-align: center;
            max-width: 800px;
            padding: 20px;
        }

        h1 {
            font-size: 5rem;
            font-weight: 800;
            letter-spacing: -2px;
            margin-bottom: 20px;
            text-shadow: 0 10px 30px rgba(0,0,0,0.5);
        }

        p.subtitle {
            font-size: 1.5rem;
            font-weight: 300;
            opacity: 0.8;
        }

        /* --- FLOATING ELEMENTS SECTION --- */
        .floating-section {
            min-height: 100vh;
            position: relative;
            padding: 100px 20px;
            display: flex;
            flex-direction: column;
            align-items: center;
            overflow: hidden; /* Penting agar elemen yang keluar batas tidak membuat scrollbar horizontal */
        }

        .content-block {
            background: rgba(255, 255, 255, 0.05);
            backdrop-filter: blur(10px);
            border: 1px solid rgba(255, 255, 255, 0.1);
            padding: 60px;
            border-radius: 30px;
            max-width: 600px;
            z-index: 5; /* Di atas elemen floating */
            margin-bottom: 50px;
            text-align: center;
        }

        .content-block h2 {
            font-size: 3rem;
            margin-bottom: 20px;
            background: linear-gradient(45deg, #ff00cc, #333399);
            -webkit-background-clip: text;
            -webkit-text-fill-color: transparent;
        }

        /* Elemen Dekoratif yang Melayang (Shapes) */
        .floater {
            position: absolute;
            border-radius: 50%;
            opacity: 0.6;
            z-index: 1; /* Di belakang konten */
        }

        .floater-1 {
            width: 150px;
            height: 150px;
            background: linear-gradient(45deg, #ff6b6b, #feca57);
            top: 10%;
            right: 10%;
            filter: blur(20px);
        }

        .floater-2 {
            width: 250px;
            height: 250px;
            background: linear-gradient(45deg, #54a0ff, #5f27cd);
            bottom: 10%;
            left: 5%;
            filter: blur(30px);
        }

        .floater-3 {
            width: 80px;
            height: 80px;
            background: #fff;
            top: 40%;
            right: 25%;
            filter: blur(10px);
        }

        /* --- FOOTER --- */
        .footer {
            height: 50vh;
            display: flex;
            align-items: center;
            justify-content: center;
            background: #111;
        }
    </style>
</head>
<body>

    <!-- 1. HERO PARALLAX -->
    <div class="hero-section">
        <div class="hero-bg"></div> <!-- Background terpisah untuk di-animate -->
        <div class="hero-overlay"></div>
        
        <div class="hero-content">
            <h1 id="heroTitle">SPACE DEPTH</h1>
            <p class="subtitle" id="heroSubtitle">Scroll untuk merasakan efek kedalaman</p>
        </div>
    </div>

    <!-- 2. FLOATING ELEMENTS PARALLAX -->
    <div class="floating-section" id="floatSection">
        
        <!-- Elemen Dekoratif (Planet/Orb) -->
        <div class="floater floater-1" data-speed="0.5"></div> <!-- Cepat -->
        <div class="floater floater-2" data-speed="0.2"></div> <!-- Lambat -->
        <div class="floater floater-3" data-speed="0.8"></div> <!-- Sangat Cepat -->

        <!-- Konten Utama -->
        <div class="content-block">
            <h2>Floating Dimension</h2>
            <p>Perhatikan bola-bola cahaya di latar belakang. Mereka bergerak dengan kecepatan yang berbeda-beda saat Anda melakukan scroll.</p>
            <br>
            <p>Elemen yang "dekat" bergerak lebih cepat, sementara elemen yang "jauh" bergerak lebih lambat. Ini menciptakan ilusi ruang 3D yang nyata.</p>
        </div>

        <div class="content-block">
            <h2>Smooth Experience</h2>
            <p>Dengan ScrollTrigger 'scrub', animasi terikat sempurna dengan jari Anda. Berhenti scroll = berhenti animasi.</p>
        </div>

    </div>

    <div class="footer">
        <p>Dibuat dengan GSAP ScrollTrigger</p>
    </div>

    <!-- Load Scripts -->
    <script src="<https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js>"></script>
    <script src="<https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/ScrollTrigger.min.js>"></script>

    <script>
        gsap.registerPlugin(ScrollTrigger);

        // ===== 1. HERO PARALLAX EFFECT =====
        // Efek: Background bergerak lebih lambat dari scroll normal (seolah-olah jauh)
        gsap.to(".hero-bg", {
            yPercent: 30, // Gerakkan gambar background ke bawah sejauh 30%
            ease: "none",
            scrollTrigger: {
                trigger: ".hero-section",
                start: "top top", // Mulai saat top hero di top layar
                end: "bottom top", // Selesai saat bottom hero keluar ke top layar
                scrub: true
            }
        });

        // Efek: Teks bergerak sedikit lebih cepat ke bawah untuk efek 'layering'
        gsap.to(".hero-content", {
            y: 150, // Teks turun 150px saat scroll
            opacity: 0, // Sekaligus fade out
            ease: "none",
            scrollTrigger: {
                trigger: ".hero-section",
                start: "top top",
                end: "bottom 20%", // Fade out lebih cepat sebelum section habis
                scrub: true
            }
        });

        // ===== 2. FLOATING ELEMENTS (MULTI-SPEED) =====
        // Kita menggunakan loop untuk menargetkan semua elemen dengan class '.floater'
        // Kecepatan ditentukan oleh atribut data-speed di HTML
        
        const floaters = document.querySelectorAll(".floater");

        floaters.forEach((floater) => {
            // Ambil kecepatan dari atribut data-speed (misal: 0.5, 0.2, 0.8)
            const speed = floater.getAttribute("data-speed");
            
            // Hitung jarak pergerakan (makin besar speed, makin jauh geraknya)
            const movement = -300 * speed; 

            gsap.to(floater, {
                y: movement, // Gerakkan elemen ke atas (nilai negatif)
                ease: "none",
                scrollTrigger: {
                    trigger: ".floating-section",
                    start: "top bottom", // Mulai saat section masuk dari bawah
                    end: "bottom top",   // Selesai saat section keluar di atas
                    scrub: 0.5 // Tambahkan sedikit smoothing (lag) supaya lebih smooth
                }
            });
        });

    </script>
</body>
</html>

Penjelasan Kode:

  1. scrub: true: Ini adalah properti paling krusial untuk parallax.
    • Tanpa scrub: Animasi berjalan sekali (play) dari awal sampai akhir dengan durasi tertentu (misal 1 detik).
    • Dengan scrub: Animasi tidak punya durasi waktu. Progres animasi (0% sampai 100%) terikat langsung dengan posisi scrollbar. Jika Anda scroll cepat, animasi cepat. Jika Anda berhenti scroll, animasi berhenti.
  2. yPercent vs y:
    • yPercent: 50: Menggerakkan elemen sejauh 50% dari tinggi elemen itu sendiri. Sangat berguna untuk background image yang ukurannya responsif.
    • y: -600: Menggerakkan elemen sejauh 600 piksel.
  3. Logika Kedalaman (Depth):
    • Di dunia nyata, benda yang jauh terlihat bergerak lebih lambat daripada benda yang dekat saat kita bergerak.
    • Di kode, kita meniru ini dengan memberikan nilai perpindahan (y) yang kecil untuk elemen "jauh" (layer-slow) dan nilai perpindahan yang besar untuk elemen "dekat" (layer-fast).

Teknik 4: Pinning Elements

Menjepit elemen di layar sementara user terus scroll, sempurna untuk section intro atau testimonial.

4. PinElement.gif
Pinning Elements

pin-element.html

<!DOCTYPE html>
<html lang="id">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>4. ScrollTrigger - Pinning Elements</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        body {
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            background-color: #111; /* Tema gelap */
            color: white;
        }

        /* --- INTRO --- */
        .intro, .outro {
            height: 100vh;
            display: flex;
            align-items: center;
            justify-content: center;
            text-align: center;
            background: #000;
        }
        
        .intro h1 {
            font-size: 4rem;
            background: linear-gradient(to right, #fff, #666);
            -webkit-background-clip: text;
            -webkit-text-fill-color: transparent;
        }

        /* --- PINNING SECTION (CONTAINER) --- */
        .pin-section {
            display: flex;
            /* Container ini tidak boleh punya height fix, 
               tingginya akan mengikuti konten teks di sebelah kanan */
            background-color: #111;
            position: relative;
        }

        /* --- SISI KIRI (GAMBAR YANG AKAN DI-PIN) --- */
        .left-content {
            width: 50%;
            height: 100vh; /* Penting: harus setinggi layar */
            display: flex;
            align-items: center;
            justify-content: center;
            /* Background gradient halus */
            background: radial-gradient(circle at center, #222 0%, #111 70%);
        }

        .product-card {
            width: 300px;
            height: 500px;
            background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
            border-radius: 30px;
            box-shadow: 0 20px 50px rgba(0,242,254, 0.2);
            display: flex;
            align-items: center;
            justify-content: center;
            font-size: 2rem;
            font-weight: bold;
            color: white;
            position: relative;
        }

        /* --- SISI KANAN (TEKS YANG AKAN SCROLL) --- */
        .right-content {
            width: 50%;
            padding: 100px 50px;
        }

        .feature-step {
            height: 100vh; /* Setiap teks memakan 1 layar penuh */
            display: flex;
            flex-direction: column;
            justify-content: center;
            padding-right: 50px;
        }

        .feature-step h2 {
            font-size: 3rem;
            margin-bottom: 20px;
            color: #4facfe;
        }

        .feature-step p {
            font-size: 1.5rem;
            line-height: 1.6;
            color: #ccc;
        }

    </style>
</head>
<body>

    <div class="intro">
        <div>
            <h1>Product Showcase</h1>
            <p>Scroll ke bawah untuk melihat fitur.</p>
        </div>
    </div>

    <!-- CONTAINER UTAMA UNTUK PINNING -->
    <div class="pin-section">
        
        <!-- SISI KIRI: Ini yang akan di-PIN (dijepit) -->
        <div class="left-content">
            <div class="product-card" id="pinnedBox">
                PHONE
            </div>
        </div>

        <!-- SISI KANAN: Ini yang akan SCROLL biasa -->
        <div class="right-content">
            
            <div class="feature-step">
                <h2>Desain Elegan</h2>
                <p>Dibuat dengan material premium yang nyaman digenggam. Sudut melengkung yang sempurna memberikan pengalaman visual tanpa batas.</p>
            </div>

            <div class="feature-step">
                <h2>Kamera Pro</h2>
                <p>Tangkap setiap momen dengan detail menakjubkan. Sistem kamera tiga lensa kami memungkinkan Anda memotret dalam kondisi minim cahaya.</p>
            </div>

            <div class="feature-step">
                <h2>Baterai Seharian</h2>
                <p>Jangan khawatir kehabisan daya. Chip efisiensi tinggi kami memastikan ponsel Anda bertahan dari pagi hingga malam.</p>
            </div>

            <div class="feature-step">
                <h2>Keamanan Tinggi</h2>
                <p>Data Anda adalah privasi Anda. Dilengkapi dengan sensor sidik jari ultrasonik dan pengenalan wajah 3D.</p>
            </div>

        </div>

    </div>

    <div class="outro">
        <h1>Pre-Order Sekarang</h1>
    </div>

    <!-- Load Scripts -->
    <script src="<https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js>"></script>
    <script src="<https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/ScrollTrigger.min.js>"></script>

    <script>
        gsap.registerPlugin(ScrollTrigger);

        // ===== LOGIKA PINNING =====
        
        ScrollTrigger.create({
            trigger: ".pin-section",    // Elemen wrapper utama yang menjadi pemicu
            start: "top top",           // Mulai saat bagian atas .pin-section menyentuh bagian atas layar
            end: "bottom bottom",       // Berakhir saat bagian bawah .pin-section menyentuh bagian bawah layar
            
            pin: ".left-content",       // Bagian mana yang mau dijepit? Sisi kiri (.left-content)
            
            // Opsi Tambahan (Optional)
            pinSpacing: false,          // false = konten bawah tidak didorong (berguna utk layout split seperti ini)
            markers: false              // Ubah ke true jika ingin melihat garis debugging
        });

        // ===== ANIMASI TAMBAHAN (BONUS) =====
        // Mari kita buat kotak HP-nya berubah warna/rotasi saat scroll
        
        gsap.to("#pinnedBox", {
            rotation: 360,              // Putar 360 derajat selama scroll
            backgroundColor: "#764ba2", // Berubah warna jadi ungu
            scrollTrigger: {
                trigger: ".pin-section",
                start: "top top",
                end: "bottom bottom",
                scrub: 1                // scrub halus
            }
        });

    </script>
</body>
</html>

Penjelasan Kode:

  1. Struktur HTML Split Screen: Kunci pinning yang sukses ada di layout CSS.
    • .pin-section (Container): Harus membungkus kedua sisi (kiri dan kanan).
    • .left-content (Pinned): Sisi yang akan diam. Biasanya tingginya di-set 100vh agar pas satu layar.
    • .right-content (Scroll): Sisi yang akan bergerak. Isinya harus lebih panjang/tinggi daripada sisi kiri agar efek pinning terlihat. Di sini saya membuat 4 bagian teks (.feature-step), yang total tingginya 400vh.
  2. ScrollTrigger.create(): Metode ini digunakan saat kita hanya ingin membuat trigger (seperti pinning) tanpa animasi gsap.to yang spesifik, atau untuk pengaturan yang lebih kompleks.
  3. pin: ".left-content": Inilah perintah ajaibnya. GSAP akan secara otomatis menambahkan position: fixed pada elemen ini selama durasi scroll berlangsung, dan mengembalikannya ke posisi semula saat scroll selesai.
  4. start & end:
    • Pinning dimulai saat container masuk layar (start: "top top").
    • Pinning berakhir saat container selesai di-scroll (end: "bottom bottom"). Artinya, gambar akan berhenti di-pin tepat saat teks terakhir selesai lewat.

Teknik 5: Scrub Animation

Mengikat animasi langsung ke posisi scroll, sempurna untuk progress bars atau animated counters.

5. Scrub.gif
Scrub Animation

scrub-animation.html

<!DOCTYPE html>
<html lang="id">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>5. ScrollTrigger - Scrub Animation</title>
    <style>
      * {
        margin: 0;
        padding: 0;
        box-sizing: border-box;
      }

      body {
        font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
        /* Overflow-x hidden penting agar mobil tidak membuat scroll horizontal */
        overflow-x: hidden;
      }

      /* --- PROGRESS BAR --- */
      .progress-container {
        position: fixed;
        top: 0;
        left: 0;
        width: 100%;
        height: 8px;
        background: rgba(0, 0, 0, 0.1);
        z-index: 100;
      }

      .progress-bar {
        height: 100%;
        background: #ff0055;
        width: 0%; /* Mulai dari 0 */
      }

      /* --- SCENE CONTAINER --- */
      .scene {
        height: 100vh;
        width: 100%;
        position: relative;
        display: flex;
        align-items: center;
        justify-content: center;
        /* Background awal (Siang) */
        background: #87ceeb;
        overflow: hidden;
      }

      /* --- MATAHARI / BULAN --- */
      .sun {
        position: absolute;
        top: 100px;
        right: 100px;
        width: 100px;
        height: 100px;
        background: #ffd700; /* Kuning Matahari */
        border-radius: 50%;
        box-shadow: 0 0 40px #ffd700;
      }

      /* --- MOBIL --- */
      .car-container {
        position: absolute;
        bottom: 100px;
        left: -200px; /* Mulai di luar layar kiri */
        width: 200px;
        height: 100px;
        z-index: 10;
      }

      .car-body {
        width: 100%;
        height: 60px;
        background: #ff4757;
        border-radius: 10px 30px 10px 10px;
        position: relative;
      }

      .car-roof {
        position: absolute;
        top: -30px;
        left: 20px;
        width: 100px;
        height: 30px;
        background: #ff4757;
        border-radius: 20px 20px 0 0;
      }

      .wheel {
        width: 40px;
        height: 40px;
        background: #333;
        border-radius: 50%;
        border: 3px solid #fff;
        position: absolute;
        bottom: -20px;
        display: flex;
        align-items: center;
        justify-content: center;
      }

      .wheel::after {
        /* Jari-jari velg */
        content: "";
        width: 80%;
        height: 3px;
        background: white;
      }

      .wheel::before {
        /* Jari-jari velg vertikal */
        content: "";
        height: 80%;
        width: 3px;
        background: white;
        position: absolute;
      }

      .wheel-front {
        right: 20px;
      }
      .wheel-back {
        left: 20px;
      }

      /* --- JALAN RAYA --- */
      .road {
        position: absolute;
        bottom: 0;
        width: 100%;
        height: 120px;
        background: #555;
        border-top: 10px solid #333;
      }

      .road-line {
        position: absolute;
        top: 50%;
        left: 0;
        width: 100%;
        height: 4px;
        background: repeating-linear-gradient(
          to right,
          white 0,
          white 50px,
          transparent 50px,
          transparent 100px
        );
      }

      /* --- TEXT --- */
      .center-text {
        position: absolute;
        text-align: center;
        color: white;
        font-size: 3rem;
        text-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
        opacity: 0;
        transform: translateY(50px);
      }

      .scroll-instruction {
        position: absolute;
        bottom: 20px;
        color: white;
        font-size: 1.2rem;
        animation: bounce 2s infinite;
      }

      @keyframes bounce {
        0%,
        100% {
          transform: translateY(0);
        }
        50% {
          transform: translateY(-10px);
        }
      }

      /* SECTION TAMBAHAN UTK SCROLL SPACE */
      .spacer {
        height: 100vh;
        background: #222;
      }
    </style>
  </head>
  <body>
    <!-- Progress Bar di atas -->
    <div class="progress-container">
      <div class="progress-bar"></div>
    </div>

    <!-- CONTAINER UTAMA (Yang akan di-pin) -->
    <div class="scene" id="mainScene">
      <div class="sun"></div>

      <div class="center-text" id="textDay">Selamat Pagi! ☀️</div>
      <div class="center-text" id="textNight">Selamat Malam! 🌙</div>

      <div class="car-container">
        <div class="car-roof"></div>
        <div class="car-body"></div>
        <div class="wheel wheel-back"></div>
        <div class="wheel wheel-front"></div>
      </div>

      <div class="road">
        <div class="road-line"></div>
      </div>

      <div class="scroll-instruction">Scroll ke bawah perlahan...</div>
    </div>

    <!-- Spacer kosong supaya ada area untuk scroll -->
    <!-- Kita butuh scroll yang panjang supaya animasinya lama -->
    <div style="height: 200vh"></div>

    <!-- Load Scripts -->
    <script src="<https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js>"></script>
    <script src="<https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/ScrollTrigger.min.js>"></script>

    <script>
      gsap.registerPlugin(ScrollTrigger);

      // Kita akan menggunakan TIMELINE yang diikat dengan ScrollTrigger
      // Timeline memudahkan kita mengatur urutan kejadian

      let tl = gsap.timeline({
        scrollTrigger: {
          trigger: "#mainScene", // Pemicu animasi
          start: "top top", // Mulai saat top scene di top layar
          end: "+=2000", // Animasi berlangsung selama 2000px scroll
          scrub: 1, // Smooth scrubbing (1 detik lag)
          pin: true, // Jepit scene agar tidak bergerak saat animasi jalan
          markers: false,
        },
      });

      // --- SEQUENCE ANIMASI (Semuanya berjalan berurutan/bersamaan sesuai timeline) ---

      // 1. Gerakkan Mobil dari Kiri ke Kanan
      tl.to(".car-container", {
        x: window.innerWidth + 400, // Bergerak sampai keluar layar kanan
        ease: "none", // Kecepatan konstan
        duration: 10, // Durasi relatif dalam timeline
      })

        // 2. Putar Roda (bersamaan dengan mobil jalan)
        // '<' artinya: mulai bersamaan dengan animasi sebelumnya (mobil jalan)
        .to(
          ".wheel",
          {
            rotation: 360 * 4, // Putar 4 kali putaran penuh
            ease: "none",
            duration: 10,
          },
          "<"
        )

        // 3. Ubah Langit dari Siang (Biru) ke Malam (Gelap)
        // Mulai sedikit setelah mobil jalan
        .to(
          "#mainScene",
          {
            backgroundColor: "#0f0c29", // Warna malam
            duration: 5,
          },
          "<2"
        )

        // 4. Ubah Matahari jadi Bulan
        .to(
          ".sun",
          {
            backgroundColor: "#ffffff", // Putih bulan
            boxShadow: "0 0 40px #ffffff", // Glow putih
            x: -200, // Bergerak sedikit ke kiri
            y: 50, // Turun sedikit
            duration: 5,
          },
          "<"
        ) // Bersamaan dengan langit berubah

        // 5. Teks Siang Muncul & Hilang
        .to("#textDay", { opacity: 1, y: 0, duration: 2 }, 0) // Muncul di awal (detik 0)
        .to("#textDay", { opacity: 0, y: -50, duration: 2 }, 3) // Hilang di detik 3

        // 6. Teks Malam Muncul
        .to("#textNight", { opacity: 1, y: 0, duration: 2 }, 7); // Muncul di detik 7 (saat langit sudah gelap)

      // --- ANIMASI PROGRESS BAR (Terpisah) ---
      // Ini untuk menunjukkan progress scroll halaman secara keseluruhan

      gsap.to(".progress-bar", {
        width: "100%",
        ease: "none",
        scrollTrigger: {
          trigger: "body",
          start: "top top",
          end: "bottom bottom",
          scrub: 0, // Langsung, tanpa lag
        },
      });
    </script>
  </body>
</html>

Penjelasan Kode:

  1. tl = gsap.timeline({ ... }):
    • Di sini kita membuat timeline utama. Timeline ini berisi semua animasi (mobil jalan, roda berputar, langit berubah).
    • pin: true: Scene "dijepit" di layar selama 2000px scroll (lihat end: "+=2000"). Artinya, user merasa seperti menonton film pendek yang dikontrol oleh scroll mereka.
    • scrub: 1: Angka 1 memberikan efek smoothing. Saat scroll berhenti, mobil tidak berhenti mendadak, tapi meluncur sedikit.
  2. Simbol < dan 0 di Timeline:
    • .to(..., "<"): Tanda < berarti "jalankan animasi ini bersamaan dengan awal animasi sebelumnya". Ini sebabnya roda berputar bersamaan dengan mobil jalan.
    • .to(..., "<2"): Jalankan animasi 2 detik setelah animasi sebelumnya mulai.
    • .to(..., 0): Jalankan animasi tepat di detik ke-0 timeline (awal banget).
  3. Progress Bar:
    • Ini menggunakan ScrollTrigger terpisah yang menargetkan body.
    • Karena targetnya body dan start: "top top"end: "bottom bottom", progress bar akan terisi penuh tepat saat user mencapai bagian paling bawah halaman.

Teknik 6: Stagger on Scroll

Membuat beberapa elemen beranimasi satu per satu saat scroll mencapainya.

6. Stagger.gif
Stagger on Scroll

stagger-on-scroll.html

<!DOCTYPE html>
<html lang="id">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>6. ScrollTrigger - Stagger Animation</title>
    <style>
      * {
        margin: 0;
        padding: 0;
        box-sizing: border-box;
      }

      body {
        font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
        background-color: #f4f4f9;
        color: #333;
      }

      /* Header sederhana */
      .header {
        height: 80vh;
        display: flex;
        flex-direction: column;
        align-items: center;
        justify-content: center;
        text-align: center;
        background: white;
      }

      .header h1 {
        font-size: 3.5rem;
        margin-bottom: 1rem;
      }
      .header p {
        font-size: 1.2rem;
        color: #666;
      }

      /* --- TEAM SECTION --- */
      .team-section {
        padding: 100px 20px;
        max-width: 1200px;
        margin: 0 auto;
      }

      .section-title {
        text-align: center;
        font-size: 2.5rem;
        margin-bottom: 60px;
        opacity: 0; /* Mulai transparan */
        transform: translateY(30px);
      }

      .grid {
        display: grid;
        grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
        gap: 40px;
      }

      .card {
        background: white;
        border-radius: 20px;
        overflow: hidden;
        box-shadow: 0 10px 30px rgba(0, 0, 0, 0.05);
        opacity: 0; /* Mulai transparan untuk animasi */
        /* Transform awal akan di-set lewat GSAP */
      }

      .card-img {
        height: 250px;
        background-color: #ddd;
        position: relative;
        overflow: hidden;
      }

      .card-img img {
        width: 100%;
        height: 100%;
        object-fit: cover;
        transition: transform 0.5s ease;
      }

      .card:hover .card-img img {
        transform: scale(1.1); /* Zoom effect saat hover */
      }

      .card-content {
        padding: 25px;
        text-align: center;
      }

      .card-content h3 {
        margin-bottom: 10px;
        color: #444;
      }
      .card-content p {
        color: #888;
        font-size: 0.9rem;
      }

      .footer {
        height: 300px;
        background: #333;
        display: flex;
        align-items: center;
        justify-content: center;
        color: white;
        margin-top: 100px;
      }
    </style>
  </head>
  <body>
    <div class="header">
      <h1>Meet The Team</h1>
      <p>Scroll ke bawah untuk melihat tim kami muncul.</p>
    </div>

    <div class="team-section">
      <h2 class="section-title">Our Creative Minds</h2>

      <div class="grid">
        <!-- Card 1 -->
        <div class="card team-card">
          <div class="card-img">
            <img
              src="<https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?ixlib=rb-4.0.3&auto=format&fit=crop&w=400&q=80>"
              alt="Member"
            />
          </div>
          <div class="card-content">
            <h3>John Doe</h3>
            <p>Lead Designer</p>
          </div>
        </div>

        <!-- Card 2 -->
        <div class="card team-card">
          <div class="card-img">
            <img
              src="<https://images.unsplash.com/photo-1494790108377-be9c29b29330?ixlib=rb-4.0.3&auto=format&fit=crop&w=400&q=80>"
              alt="Member"
            />
          </div>
          <div class="card-content">
            <h3>Jane Smith</h3>
            <p>Senior Developer</p>
          </div>
        </div>

        <!-- Card 3 -->
        <div class="card team-card">
          <div class="card-img">
            <img
              src="<https://images.unsplash.com/photo-1500648767791-00dcc994a43e?ixlib=rb-4.0.3&auto=format&fit=crop&w=400&q=80>"
              alt="Member"
            />
          </div>
          <div class="card-content">
            <h3>Mike Johnson</h3>
            <p>Product Manager</p>
          </div>
        </div>

        <!-- Card 4 -->
        <div class="card team-card">
          <div class="card-img">
            <img
              src="<https://images.unsplash.com/photo-1438761681033-6461ffad8d80?ixlib=rb-4.0.3&auto=format&fit=crop&w=400&q=80>"
              alt="Member"
            />
          </div>
          <div class="card-content">
            <h3>Sarah Wilson</h3>
            <p>Marketing Head</p>
          </div>
        </div>

        <!-- Card 5 -->
        <div class="card team-card">
          <div class="card-img">
            <img
              src="<https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-4.0.3&auto=format&fit=crop&w=400&q=80>"
              alt="Member"
            />
          </div>
          <div class="card-content">
            <h3>David Brown</h3>
            <p>UX Researcher</p>
          </div>
        </div>

        <!-- Card 6 -->
        <div class="card team-card">
          <div class="card-img">
            <img
              src="<https://images.unsplash.com/photo-1534528741775-53994a69daeb?ixlib=rb-4.0.3&auto=format&fit=crop&w=400&q=80>"
              alt="Member"
            />
          </div>
          <div class="card-content">
            <h3>Emily Davis</h3>
            <p>Content Strategist</p>
          </div>
        </div>
      </div>
    </div>

    <div class="footer">Footer Content</div>

    <!-- Load Scripts -->
    <script src="<https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js>"></script>
    <script src="<https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/ScrollTrigger.min.js>"></script>

    <script>
      gsap.registerPlugin(ScrollTrigger);

      // ===== 1. ANIMASI JUDUL SECTION =====
      gsap.to(".section-title", {
        opacity: 1,
        y: 0,
        duration: 1,
        ease: "power3.out",
        scrollTrigger: {
          trigger: ".section-title",
          start: "top 80%", // Mulai saat judul masuk 80% viewport
        },
      });

      // ===== 2. ANIMASI STAGGER KARTU TIM =====
      // Kita menargetkan SEMUA elemen dengan class '.team-card' sekaligus

      gsap.fromTo(
        ".team-card",
        // FROM state (Kondisi awal)
        {
          y: 100, // Mulai dari 100px di bawah
          opacity: 0, // Mulai transparan
        },
        // TO state (Kondisi akhir)
        {
          y: 0, // Kembali ke posisi normal
          opacity: 1, // Muncul penuh
          duration: 0.8,
          ease: "back.out(1.7)", // Efek 'pop' atau memantul sedikit saat muncul

          // PENGATURAN STAGGER
          stagger: 0.2, // Jeda 0.2 detik antar setiap kartu!

          scrollTrigger: {
            trigger: ".grid", // Pemicunya adalah container grid-nya
            start: "top 75%", // Mulai saat grid masuk 75% viewport
            toggleActions: "play none none reverse",
            // play: mainkan saat masuk
            // reverse: mainkan mundur saat scroll balik ke atas (keluar viewport)
          },
        }
      );

      // PENJELASAN STAGGER:
      // Tanpa stagger, ke-6 kartu akan muncul bersamaan (jederr!).
      // Dengan stagger: 0.2:
      // Kartu 1 muncul di detik 0.0
      // Kartu 2 muncul di detik 0.2
      // Kartu 3 muncul di detik 0.4
      // dst...
      // Ini menciptakan efek gelombang yang sangat elegan.
    </script>
  </body>
</html>

Penjelasan Kode Penting:

  1. gsap.fromTo(): Berbeda dengan gsap.to (hanya tujuan) atau gsap.from (hanya asal), fromTo memberikan kontrol penuh atas kedua keadaan.
    • Kita menentukan secara eksplisit: "Mulai dari y: 100, opacity: 0 DAN berakhir di y: 0, opacity: 1". Ini sangat aman untuk memastikan animasi selalu konsisten.
  2. stagger: 0.2: Inilah bintangnya. Properti ini memberitahu GSAP: "Jangan animasikan semua elemen .team-card sekaligus. Beri jeda 0.2 detik di antara masing-masing elemen."
  3. toggleActions: "play none none reverse":
    • play: Saat scroll ke bawah dan elemen masuk layar -> Jalankan animasi.
    • reverse: Saat scroll kembali ke atas dan elemen keluar layar -> Jalankan animasi mundur (kartu menghilang lagi ke bawah). Ini membuat website terasa sangat responsif dan hidup.

Teknik 7: Horizontal Scroll

Membuat scroll horizontal yang dramatic sambil user melakukan scroll vertical.

7. Horizontal.gif
Horizontal Scroll

horizontal-scroll.html

<!DOCTYPE html>
<html lang="id">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>7. ScrollTrigger - Horizontal Scroll</title>
    <style>
      * {
        margin: 0;
        padding: 0;
        box-sizing: border-box;
      }

      body {
        font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
        overflow-x: hidden; /* Penting: hilangkan scrollbar horizontal default */
        background-color: #111;
        color: white;
      }

      /* Section Vertikal Biasa (Intro) */
      .vertical-section {
        height: 100vh;
        display: flex;
        flex-direction: column;
        align-items: center;
        justify-content: center;
        border-bottom: 1px solid #333;
        background: #000;
        z-index: 2;
        position: relative;
      }

      .vertical-section h1 {
        font-size: 4rem;
        margin-bottom: 20px;
        background: linear-gradient(to right, #ff00cc, #333399);
        -webkit-background-clip: text;
        -webkit-text-fill-color: transparent;
      }

      .scroll-down {
        margin-top: 50px;
        animation: bounce 2s infinite;
      }

      @keyframes bounce {
        0%,
        100% {
          transform: translateY(0);
        }
        50% {
          transform: translateY(10px);
        }
      }

      /* --- HORIZONTAL CONTAINER WRAPPER --- */
      /* Container ini akan di-PIN (dijepit) selama animasi horizontal berlangsung */
      .horizontal-container {
        width: 100%;
        height: 100vh; /* Tinggi layar penuh */
        background-color: #222;
        overflow: hidden; /* Sembunyikan konten yang melebar ke kanan */
        display: flex; /* Flex row untuk menyusun panel ke samping */
      }

      /* Track yang akan bergerak ke kiri */
      .horizontal-track {
        display: flex;
        flex-wrap: nowrap; /* Jangan wrap ke bawah */
        height: 100%;
      }

      /* Panel Individual */
      .panel {
        width: 100vw; /* Setiap panel selebar layar */
        height: 100%;
        display: flex;
        align-items: center;
        justify-content: center;
        font-size: 3rem;
        font-weight: bold;
        position: relative;
        flex-shrink: 0; /* Jangan menyusut */
      }

      .panel-1 {
        background: #ff9a9e;
        color: #333;
      }
      .panel-2 {
        background: #fad0c4;
        color: #333;
      }
      .panel-3 {
        background: #a18cd1;
        color: white;
      }
      .panel-4 {
        background: #fbc2eb;
        color: #333;
      }

      .panel img {
        max-width: 80%;
        max-height: 70vh;
        border-radius: 20px;
        box-shadow: 0 20px 50px rgba(0, 0, 0, 0.3);
      }

      .panel-content {
        text-align: center;
        padding: 20px;
      }

      .panel-number {
        position: absolute;
        top: 50px;
        left: 50px;
        font-size: 8rem;
        opacity: 0.3;
        font-weight: 900;
      }
    </style>
  </head>
  <body>
    <!-- Intro Section (Scroll Vertikal) -->
    <div class="vertical-section">
      <h1>Horizontal Gallery</h1>
      <p>Scroll ke bawah untuk masuk ke mode horizontal.</p>
      <div class="scroll-down">↓</div>
    </div>

    <!-- Horizontal Section (Akan di-Pin) -->
    <div class="horizontal-container">
      <!-- Track ini yang akan kita gerakkan ke kiri -->
      <div class="horizontal-track">
        <div class="panel panel-1">
          <div class="panel-number">01</div>
          <div class="panel-content">
            <h2>Section Satu</h2>
            <p style="font-size: 1.5rem">Mulai perjalanan horizontal.</p>
          </div>
        </div>

        <div class="panel panel-2">
          <div class="panel-number">02</div>
          <img
            src="<https://images.unsplash.com/photo-1493246507139-91e8fad9978e?ixlib=rb-4.0.3&auto=format&fit=crop&w=800&q=80>"
            alt="Landscape"
          />
        </div>

        <div class="panel panel-3">
          <div class="panel-number">03</div>
          <div class="panel-content">
            <h2>Section Tiga</h2>
            <p style="font-size: 1.5rem">Terus scroll ke bawah...</p>
          </div>
        </div>

        <div class="panel panel-4">
          <div class="panel-number">04</div>
          <img
            src="<https://images.unsplash.com/photo-1518173946687-a4c8892bbd9f?ixlib=rb-4.0.3&auto=format&fit=crop&w=800&q=80>"
            alt="Nature"
          />
        </div>
      </div>
    </div>

    <!-- Outro Section (Kembali Vertikal) -->
    <div class="vertical-section" style="background: #111">
      <h1>Selesai</h1>
      <p>Kembali ke scroll vertikal normal.</p>
    </div>

    <!-- Load Scripts -->
    <script src="<https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js>"></script>
    <script src="<https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/ScrollTrigger.min.js>"></script>

    <script>
      gsap.registerPlugin(ScrollTrigger);

      // Ambil elemen track yang berisi semua panel
      const track = document.querySelector(".horizontal-track");

      // Ambil semua panel untuk menghitung lebar total
      const panels = gsap.utils.toArray(".panel");

      // ===== LOGIKA HORIZONTAL SCROLL =====

      gsap.to(track, {
        // Gerakkan track ke kiri (x negatif)
        // Sejauh: (jumlah panel - 1) * 100% lebar layar
        // Contoh: kalau ada 4 panel, kita geser 3 kali layar ke kiri agar panel terakhir pas di layar
        xPercent: -100 * (panels.length - 1),

        ease: "none", // Penting: linear movement agar sinkron dengan scroll

        scrollTrigger: {
          trigger: ".horizontal-container", // Pemicu adalah container pembungkus
          pin: true, // JEPIT container selama animasi berlangsung
          scrub: 1, // Smooth scrubbing (1 detik lag)

          // Trik untuk menghitung durasi scroll
          // "end" menentukan seberapa panjang user harus scroll untuk menyelesaikan animasi horizontal
          // Semakin besar angkanya, semakin lambat/panjang scrollnya
          // Di sini kita set: lebar container * 3 (agar tidak terlalu cepat)
          end: () =>
            "+=" +
            document.querySelector(".horizontal-container").offsetWidth * 3,
        },
      });

      // PENJELASAN:
      // 1. Saat '.horizontal-container' mencapai top layar, ScrollTrigger akan mengaktifkan 'pin: true'.
      // 2. Layar akan tertahan di posisi itu.
      // 3. Saat user terus scroll ke bawah, GSAP akan menerjemahkan scroll vertikal itu menjadi
      //    perubahan nilai 'xPercent' pada '.horizontal-track'.
      // 4. Track bergerak ke kiri, seolah-olah user scroll horizontal.
      // 5. Setelah mencapai akhir perhitungan 'end', pin dilepas dan user scroll ke bawah lagi (masuk ke footer).
    </script>
  </body>
</html>

Penjelasan:

  1. Struktur CSS:
    • Container (.horizontal-container) harus overflow: hidden untuk menyembunyikan panel-panel yang belum masuk layar.
    • Track (.horizontal-track) harus display: flex agar panel berjejer ke samping.
    • Panel (.panel) lebarnya harus 100vw (lebar viewport penuh) dan flex-shrink: 0 agar tidak menyempit.
  2. Matematika xPercent:
    • Rumus: 100 * (panels.length - 1).
    • Jika ada 4 panel, kita ingin panel 1 terlihat di awal (0%), lalu geser sampai panel 4 terlihat.
    • Berarti kita perlu menggeser track sejauh 3 panel ke kiri (300%).
    • Jadi xPercent targetnya adalah 300.
  3. Durasi Scroll (end):
    • end: () => "+=" + ... offsetWidth * 3.
    • Ini menentukan "rasa" scrollnya. Jika kita set terlalu pendek, scroll horizontal akan terasa sangat cepat (ngebut). Jika dikali 3 atau 4, user harus scroll lebih banyak untuk menggerakkan panel, sehingga terasa lebih terkontrol dan enak dilihat.

Teknik 8: Text Counter Animation

Membuat angka atau statistik yang "count up" saat scroll mencapainya.

8. Counter.gif
Text Counter Animation

counter-animation.html

<!DOCTYPE html>
<html lang="id">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>8. ScrollTrigger - Animated Counter</title>
    <style>
      * {
        margin: 0;
        padding: 0;
        box-sizing: border-box;
      }

      body {
        font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
        background-color: #f8f9fa;
        color: #333;
      }

      /* Intro Spacer */
      .spacer {
        height: 100vh;
        display: flex;
        align-items: center;
        justify-content: center;
        background: linear-gradient(135deg, #6a11cb 0%, #2575fc 100%);
        color: white;
        font-size: 2rem;
        font-weight: bold;
        text-align: center;
      }

      /* --- STATS SECTION --- */
      .stats-section {
        padding: 100px 20px;
        background: white;
        text-align: center;
      }

      .stats-grid {
        display: grid;
        grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
        gap: 50px;
        max-width: 1200px;
        margin: 0 auto;
      }

      .stat-item {
        padding: 20px;
      }

      .stat-icon {
        font-size: 3rem;
        margin-bottom: 20px;
        display: block;
      }

      .stat-number {
        font-size: 4rem;
        font-weight: 900;
        color: #2575fc;
        line-height: 1;
        display: block;
        margin-bottom: 10px;
        font-variant-numeric: tabular-nums; /* Penting agar angka tidak 'goyang' saat berubah lebar */
      }

      .stat-label {
        font-size: 1.2rem;
        color: #666;
        text-transform: uppercase;
        letter-spacing: 1px;
        font-weight: 600;
      }

      /* Outro Spacer */
      .outro {
        height: 50vh;
        background: #333;
        color: white;
        display: flex;
        align-items: center;
        justify-content: center;
      }
    </style>
  </head>
  <body>
    <div class="spacer">
      <div>
        <h1>Our Achievements</h1>
        <p style="font-size: 1rem; opacity: 0.8">
          Scroll ke bawah untuk melihat data
        </p>
      </div>
    </div>

    <div class="stats-section">
      <div class="stats-grid">
        <!-- Stat 1 -->
        <div class="stat-item">
          <span class="stat-icon">🚀</span>
          <!-- data-target: Nilai akhir yang ingin dicapai -->
          <span class="stat-number" data-target="150">0</span>
          <span class="stat-label">Projects Completed</span>
        </div>

        <!-- Stat 2 -->
        <div class="stat-item">
          <span class="stat-icon">😊</span>
          <span class="stat-number" data-target="98">0</span>
          <span class="stat-label">Happy Clients (%)</span>
        </div>

        <!-- Stat 3 -->
        <div class="stat-item">
          <span class="stat-icon">🏆</span>
          <span class="stat-number" data-target="12">0</span>
          <span class="stat-label">Awards Won</span>
        </div>

        <!-- Stat 4 -->
        <div class="stat-item">
          <span class="stat-icon">☕</span>
          <span class="stat-number" data-target="5000">0</span>
          <span class="stat-label">Coffee Cups</span>
        </div>
      </div>
    </div>

    <div class="outro">
      <h2>Terima Kasih</h2>
    </div>

    <!-- Load Scripts -->
    <script src="<https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js>"></script>
    <script src="<https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/ScrollTrigger.min.js>"></script>

    <script>
      gsap.registerPlugin(ScrollTrigger);

      // Ambil semua elemen yang memiliki class .stat-number
      const counters = document.querySelectorAll(".stat-number");

      // Loop untuk setiap angka
      counters.forEach((counter) => {
        // Ambil nilai target dari atribut data-target (misal: 150, 98, dll)
        // Kita bersihkan karakter non-angka jika ada
        const targetValue = parseInt(counter.getAttribute("data-target"));

        // Objek dummy untuk di-animate oleh GSAP
        // GSAP tidak bisa animate innerHTML/innerText secara langsung dengan angka yang smooth
        // Jadi kita animate properti dari objek JavaScript biasa
        let obj = { val: 0 };

        gsap.to(obj, {
          val: targetValue, // Animate nilai 'val' dari 0 ke targetValue
          duration: 2, // Durasi animasi 2 detik
          ease: "power2.out", // Melambat di akhir

          scrollTrigger: {
            trigger: ".stats-section", // Pemicu adalah container section
            start: "top 80%", // Mulai saat section masuk 80% layar
            toggleActions: "play none none reverse", // Reset jika scroll balik ke atas
          },

          // Callback yang dijalankan setiap frame animasi
          onUpdate: function () {
            // Update teks di HTML dengan nilai 'val' saat ini
            // Math.ceil/round untuk membulatkan angka (hilangkan desimal)
            counter.innerText = Math.ceil(obj.val);

            // Opsional: Tambahkan tanda '+' atau '%' jika perlu
            // if (targetValue === 98) counter.innerText += "%";
          },
        });
      });

      // PENJELASAN TEKNIS:
      // 1. GSAP animate properti 'val' pada objek 'obj' dari 0 -> Target.
      // 2. Setiap frame (onUpdate), kita ambil nilai 'val' tersebut,
      //    kita bulatkan, lalu kita masukkan ke dalam HTML element.
      // 3. Ini menciptakan efek counter yang sangat smooth.
    </script>
  </body>
</html>

Penjelasan:

  1. Data Attributes (data-target):
    • Cara terbaik untuk menyimpan nilai target adalah di HTML itu sendiri (<span data-target="150">0</span>).
    • Ini membuat kode JavaScript kita bersih dan reusable. Kita tidak perlu hardcode angka di JS.
  2. Proxy Object (let obj = { val: 0 }):
    • Ini trik penting. Kita tidak bisa langsung menyuruh GSAP "Animasikan innerText dari '0' ke '150'".
    • Sebaliknya, kita menyuruh GSAP menganimasikan variabel angka biasa (obj.val).
    • Lalu, kita menggunakan fungsi onUpdate untuk mengambil nilai variabel yang sedang berubah itu, dan menempelkannya ke HTML (innerText).
  3. font-variant-numeric: tabular-nums:
    • Ini adalah properti CSS yang sering dilupakan.
    • Tanpa ini, angka "1" biasanya lebih tipis daripada angka "8". Saat counter berjalan cepat, lebar teks akan berubah-ubah (jittery/bergoyang).
    • tabular-nums memaksa semua angka memiliki lebar yang sama (seperti di tabel excel), sehingga animasi terlihat stabil dan profesional.

Tips & Best Practices ScrollTrigger

1. Gunakan markers saat Development

javascriptmarkers: true *// Aktifkan untuk debugging, tapi nonaktifkan saat production*

2. Optimize Performance

  • Hindari terlalu banyak ScrollTriggers sekaligus
  • Gunakan willChange CSS untuk elemen yang dianimasikan
  • Debounce atau throttle jika perlu

3. Mobile Considerations

  • Parallax bisa lag di mobile, gunakan dengan bijak
  • Test semua animasi di real mobile devices
  • Pertimbangkan mengurangi kompleksitas untuk mobile

4. Accessibility

  • Berikan option untuk disable animations (prefers-reduced-motion)
  • Pastikan content readable bahkan tanpa animasi
  • Gunakan prefers-reduced-motion CSS media query

css@media (prefers-reduced-motion: reduce) { * { animation: none !important; } }

5. Start Simple

  • Mulai dengan fade-in atau slide-in basic
  • Gradually explore parallax dan pinning
  • Test setiap teknik sebelum combine

Kesimpulan

ScrollTrigger adalah tool yang powerful untuk menciptakan website yang truly interactive dan engaging. Dari fade-in sederhana hingga parallax yang kompleks, ScrollTrigger membuat semua menjadi mudah.

Teknik yang telah kalian pelajari

  1. Fade on Scroll: Dasar visibilitas.
  2. Slide on Scroll: Gerakan masuk directional.
  3. Parallax (Pro Version): Kedalaman visual yang mewah.
  4. Pinning: Storytelling split-screen.
  5. Scrub Animation: Interaktif (mobil & siang/malam).
  6. Stagger: Galeri tim yang elegan.
  7. Horizontal Scroll: Pengalaman navigasi unik.
  8. Counter: Data visualisasi dinamis.

Key Takeaways:

  • ScrollTrigger membuat animasi berdasarkan scroll user position
  • Sempurna untuk meningkatkan engagement dan user experience
  • Mudah diimplementasikan dengan GSAP
  • Works perfectly di semua device termasuk mobile
  • Kombinasikan dengan timeline untuk hasil yang lebih powerful

Jangan takut untuk eksperimen dengan berbagai kombinasi start/end points, scrub values, dan easing functions. Setiap kombinasi akan menciptakan efek yang unik dan menarik!

Next Steps:

  • Praktik dengan file-file terpisah yang sudah disediakan
  • Combine beberapa teknik untuk membuat landing page Anda sendiri
  • Explore advanced ScrollTrigger features seperti toggleActions dan invalidateOnRefresh

Happy scrolling and animating! 🚀📜


by Arief Taufik Rahman