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:
- Memicu animasi saat elemen masuk viewport (area yang terlihat di layar)
- Membuat animasi yang berjalan seiring pergerakan scroll (scrubbing)
- "Menjepit" elemen di layar saat user scroll (pinning)
- Membuat parallax effects di mana elemen bergerak pada kecepatan berbeda
- Membuat scroll-driven animations yang kompleks dan sinematik
Mengapa ScrollTrigger Penting?
- Meningkatkan User Engagement: Animasi pada scroll membuat user lebih engaged dengan content.
- Menceritakan Story: Animasi bisa digunakan untuk guide user melalui narrative atau flow tertentu.
- Meningkatkan Perceived Performance: Animasi membuat loading atau tunggu terasa lebih cepat.
- Creating "Wow" Moment: Animasi scroll yang baik membuat user memorable dengan website Anda.
- 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.

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.

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:
xproperty: Menggerakkan elemen secara horizontalx: -300= ke kirix: 300= ke kananx: 0= posisi normal
yproperty: Menggerakkan elemen secara vertikaly: -200= ke atasy: 200= ke bawah
gsap.from(): Animasi dimulai DARI state yang didefinisikan, menuju state normal- Di sini, elemen mulai dari posisi di luar viewport, lalu bergerak masuk
scrollTrigger: Object yang mengatur kapan animasi dimulaitrigger: Elemen mana yang akan trigger animasistart: "top 80%": Mulai saat top elemen mencapai 80% dari layarend: "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.

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:
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.
- Tanpa
yPercentvsy: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.
- 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.

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:
- 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-set100vhagar 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 tingginya400vh.
ScrollTrigger.create(): Metode ini digunakan saat kita hanya ingin membuat trigger (seperti pinning) tanpa animasigsap.toyang spesifik, atau untuk pengaturan yang lebih kompleks.pin: ".left-content": Inilah perintah ajaibnya. GSAP akan secara otomatis menambahkanposition: fixedpada elemen ini selama durasi scroll berlangsung, dan mengembalikannya ke posisi semula saat scroll selesai.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.
- Pinning dimulai saat container masuk layar (
Teknik 5: Scrub Animation
Mengikat animasi langsung ke posisi scroll, sempurna untuk progress bars atau animated counters.

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:
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 selama2000pxscroll (lihatend: "+=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.
- Simbol
<dan0di 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).
- Progress Bar:
- Ini menggunakan
ScrollTriggerterpisah yang menargetkanbody. - Karena targetnya
bodydanstart: "top top",end: "bottom bottom", progress bar akan terisi penuh tepat saat user mencapai bagian paling bawah halaman.
- Ini menggunakan
Teknik 6: Stagger on Scroll
Membuat beberapa elemen beranimasi satu per satu saat scroll mencapainya.

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:
gsap.fromTo(): Berbeda dengangsap.to(hanya tujuan) ataugsap.from(hanya asal),fromTomemberikan kontrol penuh atas kedua keadaan.- Kita menentukan secara eksplisit: "Mulai dari
y: 100, opacity: 0DAN berakhir diy: 0, opacity: 1". Ini sangat aman untuk memastikan animasi selalu konsisten.
- Kita menentukan secara eksplisit: "Mulai dari
stagger: 0.2: Inilah bintangnya. Properti ini memberitahu GSAP: "Jangan animasikan semua elemen.team-cardsekaligus. Beri jeda 0.2 detik di antara masing-masing elemen."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.

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:
- Struktur CSS:
- Container (
.horizontal-container) harusoverflow: hiddenuntuk menyembunyikan panel-panel yang belum masuk layar. - Track (
.horizontal-track) harusdisplay: flexagar panel berjejer ke samping. - Panel (
.panel) lebarnya harus100vw(lebar viewport penuh) danflex-shrink: 0agar tidak menyempit.
- Container (
- 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
xPercenttargetnya adalah300.
- Rumus:
- 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.

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:
- 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.
- Cara terbaik untuk menyimpan nilai target adalah di HTML itu sendiri (
- Proxy Object (
let obj = { val: 0 }):- Ini trik penting. Kita tidak bisa langsung menyuruh GSAP "Animasikan
innerTextdari '0' ke '150'". - Sebaliknya, kita menyuruh GSAP menganimasikan variabel angka biasa (
obj.val). - Lalu, kita menggunakan fungsi
onUpdateuntuk mengambil nilai variabel yang sedang berubah itu, dan menempelkannya ke HTML (innerText).
- Ini trik penting. Kita tidak bisa langsung menyuruh GSAP "Animasikan
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-numsmemaksa 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
willChangeCSS 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-motionCSS 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
- Fade on Scroll: Dasar visibilitas.
- Slide on Scroll: Gerakan masuk directional.
- Parallax (Pro Version): Kedalaman visual yang mewah.
- Pinning: Storytelling split-screen.
- Scrub Animation: Interaktif (mobil & siang/malam).
- Stagger: Galeri tim yang elegan.
- Horizontal Scroll: Pengalaman navigasi unik.
- 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
toggleActionsdaninvalidateOnRefresh
Happy scrolling and animating! 🚀📜
by Arief Taufik Rahman