Table of Contents
▼- Konsep Dasar: Bagaimana Scroll-Driven Animation Bekerja di Browser
- Setup Awal: Container, @keyframes, dan animation-timeline
- Teknik Opposing Direction: Satu Kolom Naik, Satu Kolom Turun
- Menambahkan Easing dan Timing yang Natural
- Optimasi Performa dan Fallback untuk Browser Lama
- Variasi Kreatif yang Bisa Langsung Dicoba
- Ringkasan Teknik
Pernah melihat landing page di mana dua kolom gambar bergerak ke arah berlawanan saat kamu scroll? Satu kolom naik, satu kolom turun, menciptakan efek parallax yang terasa hidup dan premium.
Dulu efek ini butuh puluhan baris JavaScript dan library tambahan seperti GSAP atau ScrollMagic. Sekarang, dengan CSS Scroll-Driven Animations yang sudah didukung browser modern, kamu bisa membangunnya murni dengan CSS saja.
Artikel ini memandu kamu langkah demi langkah, dari konsep dasar sampai implementasi opposing direction yang siap pakai di landing page.
Konsep Dasar: Bagaimana Scroll-Driven Animation Bekerja di Browser
Scroll-Driven Animation adalah fitur CSS yang menghubungkan progress animasi dengan posisi scroll, bukan dengan waktu.
Normalnya, animasi CSS berjalan berdasarkan durasi (animation-duration: 1s). Dengan scroll-driven animation, animasi berjalan berdasarkan seberapa jauh pengguna sudah scroll.
Ada dua jenis timeline utama yang perlu kamu pahami:
1. scroll() timeline
Menghubungkan animasi dengan scroll progress pada elemen tertentu atau viewport secara keseluruhan.
2. view() timeline
Menghubungkan animasi dengan seberapa banyak elemen terlihat di dalam viewport.
Untuk efek opposing direction pada dua kolom, kita akan menggunakan scroll() timeline karena kita ingin keduanya bergerak seiring scroll halaman, bukan berdasarkan visibilitas masing-masing.
Properti kuncinya adalah animation-timeline. Properti ini yang memberitahu browser: "jangan jalankan animasi ini berdasarkan waktu, tapi berdasarkan scroll."
.element {
animation: moveUp linear;
animation-timeline: scroll();
animation-fill-mode: both;
}
Sesederhana itu. Tidak ada event listener, tidak ada window.addEventListener('scroll', ...).
Setup Awal: Container, @keyframes, dan animation-timeline
Mari mulai dari struktur HTML yang akan kita gunakan.
<section class="scroll-gallery">
<div class="column column-up">
<img src="foto1.jpg" alt="Foto 1">
<img src="foto2.jpg" alt="Foto 2">
<img src="foto3.jpg" alt="Foto 3">
<img src="foto4.jpg" alt="Foto 4">
</div>
<div class="column column-down">
<img src="foto5.jpg" alt="Foto 5">
<img src="foto6.jpg" alt="Foto 6">
<img src="foto7.jpg" alt="Foto 7">
<img src="foto8.jpg" alt="Foto 8">
</div>
</section>
Dua kolom sederhana. Kolom kiri akan bergerak ke atas, kolom kanan akan bergerak ke bawah saat halaman di-scroll.
Sekarang CSS dasar untuk layout-nya:
.scroll-gallery {
display: flex;
gap: 1rem;
overflow: hidden;
height: 600px; /* Tinggi viewport area */
}
.column {
display: flex;
flex-direction: column;
gap: 1rem;
flex: 1;
}
.column img {
width: 100%;
aspect-ratio: 3 / 4;
object-fit: cover;
border-radius: 12px;
}
Dengan overflow: hidden pada container, gambar yang bergerak keluar batas akan tersembunyi, menciptakan efek yang rapi.
Langkah berikutnya adalah mendefinisikan @keyframes untuk masing-masing arah.
@keyframes scrollUp {
from {
transform: translateY(0);
}
to {
transform: translateY(-30%);
}
}
@keyframes scrollDown {
from {
transform: translateY(-30%);
}
to {
transform: translateY(0);
}
}
Perhatikan logikanya. scrollUp memindahkan kolom dari posisi 0 ke atas (nilai negatif), sedangkan scrollDown memindahkan kolom dari atas ke posisi normal. Keduanya bergerak di arah berlawanan saat animasi berjalan maju seiring scroll.
Teknik Opposing Direction: Satu Kolom Naik, Satu Kolom Turun
Inilah inti dari tutorial ini. Kita menerapkan dua animasi berbeda pada masing-masing kolom, keduanya terhubung ke timeline scroll yang sama.
.column-up {
animation: scrollUp linear both;
animation-timeline: scroll(root);
}
.column-down {
animation: scrollDown linear both;
animation-timeline: scroll(root);
}
scroll(root) berarti animasi mengikuti scroll progress dari elemen root, yaitu seluruh halaman.
Hasilnya sudah terlihat efektif. Tapi ada satu masalah kecil: animasi ini berjalan sepanjang seluruh halaman, bukan hanya saat section gallery terlihat.
Untuk membatasinya agar hanya aktif saat section terlihat, kita bisa menggunakan pendekatan berbeda.
Menggunakan view-timeline untuk Kontrol Lebih Presisi
.scroll-gallery {
view-timeline: --galleryTimeline block;
}
.column-up {
animation: scrollUp linear both;
animation-timeline: --galleryTimeline;
animation-range: entry 0% exit 100%;
}
.column-down {
animation: scrollDown linear both;
animation-timeline: --galleryTimeline;
animation-range: entry 0% exit 100%;
}
Dengan view-timeline, kita membuat timeline kustom bernama --galleryTimeline yang terikat pada visibilitas .scroll-gallery di viewport.
animation-range: entry 0% exit 100% berarti animasi mulai berjalan saat elemen mulai masuk viewport dan selesai saat elemen keluar sepenuhnya.
Efeknya menjadi jauh lebih natural karena kolom hanya bergerak saat section relevan terlihat.
Hasil CSS Lengkap
/* Layout */
.scroll-gallery {
display: flex;
gap: 1rem;
overflow: hidden;
height: 600px;
view-timeline: --galleryTimeline block;
}
.column {
display: flex;
flex-direction: column;
gap: 1rem;
flex: 1;
}
.column img {
width: 100%;
aspect-ratio: 3 / 4;
object-fit: cover;
border-radius: 12px;
}
/* Keyframes */
@keyframes scrollUp {
from { transform: translateY(0); }
to { transform: translateY(-30%); }
}
@keyframes scrollDown {
from { transform: translateY(-30%); }
to { transform: translateY(0); }
}
/* Animasi */
.column-up {
animation: scrollUp linear both;
animation-timeline: --galleryTimeline;
animation-range: entry 0% exit 100%;
}
.column-down {
animation: scrollDown linear both;
animation-timeline: --galleryTimeline;
animation-range: entry 0% exit 100%;
}
Dengan kode di atas, efek opposing direction sudah berfungsi penuh tanpa satu pun baris JavaScript.
Menambahkan Easing dan Timing yang Natural
Nilai linear membuat gerakan terasa mekanis. Untuk efek yang lebih natural dan terasa seperti physics, kita bisa bereksperimen dengan animation-timing-function.
Tapi ada caveat penting: scroll-driven animation dan easing non-linear tidak selalu cocok.
Ketika timing function seperti ease-in-out diterapkan, pergerakan tidak lagi proporsional dengan scroll. Ini bisa terasa janggal karena kolom bergerak lebih cepat atau lambat dari kecepatan scroll tangan pengguna.
Rekomendasi terbaik untuk scroll-linked animation adalah tetap menggunakan linear dan sebagai gantinya bermain dengan nilai translateY untuk mengatur "kecepatan" relatif.
/* Kolom kiri bergerak lebih jauh = terasa lebih cepat */
@keyframes scrollUpFast {
from { transform: translateY(0); }
to { transform: translateY(-45%); }
}
/* Kolom kanan bergerak lebih pendek = terasa lebih lambat */
@keyframes scrollDownSlow {
from { transform: translateY(-15%); }
to { transform: translateY(0); }
}
Perbedaan jarak tempuh ini menciptakan efek depth yang terasa lebih dinamis.
Kalau ingin menambahkan sedikit "breathing room" di awal animasi sebelum kolom mulai bergerak, gunakan animation-range yang lebih ketat:
.column-up {
animation: scrollUpFast linear both;
animation-timeline: --galleryTimeline;
animation-range: entry 20% exit 80%;
}
Dengan entry 20%, animasi baru mulai berjalan setelah elemen sudah 20% masuk viewport. Ini memberikan transisi yang lebih smooth.
Optimasi Performa dan Fallback untuk Browser Lama
Performa
CSS Scroll-Driven Animation secara default berjalan di compositor thread, bukan main thread JavaScript. Ini artinya animasi tetap smooth meski halaman sedang loading konten lain.
Tapi ada dua aturan yang harus selalu dipatuhi:
Gunakan hanya transform dan opacity.
Kedua properti ini dioptimasi untuk compositor thread. Jangan animasikan top, left, margin, atau height karena itu memicu layout recalculation yang berat.
/* ✅ Benar - berjalan di compositor */
transform: translateY(-30%);
/* ❌ Hindari - memicu layout recalculation */
margin-top: -100px;
top: -100px;
Tambahkan will-change jika perlu.
.column-up,
.column-down {
will-change: transform;
}
Gunakan ini dengan bijak. will-change mengkonsumsi memori GPU, jadi jangan terapkan pada terlalu banyak elemen sekaligus.
Fallback untuk Browser Lama
Per pertengahan 2026, Chrome, Edge, dan Opera sudah mendukung scroll-driven animation dengan baik. Firefox juga sudah mendukung sejak versi 110+. Safari baru menambahkan support di versi 18.
Untuk browser yang belum mendukung, gunakan @supports:
/* Posisi statis sebagai fallback */
.column-up,
.column-down {
/* Tampilan default tanpa animasi */
}
/* Animasi hanya aktif di browser yang mendukung */
@supports (animation-timeline: scroll()) {
.column-up {
animation: scrollUp linear both;
animation-timeline: --galleryTimeline;
animation-range: entry 0% exit 100%;
}
.column-down {
animation: scrollDown linear both;
animation-timeline: --galleryTimeline;
animation-range: entry 0% exit 100%;
}
}
Dengan pendekatan ini, pengguna di browser lama tetap melihat layout yang rapi, hanya tanpa efek animasinya.
Kalau kamu ingin memberikan pengalaman yang lebih dekat untuk browser lama, pertimbangkan lightweight JavaScript fallback hanya di dalam blok @supports not:
// Fallback minimal - hanya berjalan jika CSS tidak support
if (!CSS.supports('animation-timeline', 'scroll()')) {
window.addEventListener('scroll', () => {
const scrolled = window.scrollY;
document.querySelector('.column-up').style.transform =
`translateY(${-scrolled * 0.1}px)`;
document.querySelector('.column-down').style.transform =
`translateY(${scrolled * 0.1}px)`;
}, { passive: true });
}
Gunakan { passive: true } pada event listener scroll untuk memastikan performa tidak terdampak.
Variasi Kreatif yang Bisa Langsung Dicoba
Teknik opposing direction ini tidak terbatas pada dua kolom vertikal saja.
Tiga kolom dengan kecepatan berbeda: Kolom pertama bergerak ke atas cepat, kolom tengah statis, kolom ketiga bergerak ke bawah lambat. Efeknya menciptakan ilusi kedalaman yang kuat.
Teks dan gambar berlawanan: Gambar bergerak ke bawah sementara overlay teks bergerak ke atas. Kombinasi ini sering dipakai di hero section landing page premium.
Horizontal opposing:
Ganti translateY dengan translateX. Dua baris gambar horizontal, satu bergerak ke kiri, satu ke kanan. Cocok untuk menampilkan gallery logo klien atau testimonial.
@keyframes slideLeft {
from { transform: translateX(0); }
to { transform: translateX(-20%); }
}
@keyframes slideRight {
from { transform: translateX(-20%); }
to { transform: translateX(0); }
}
Kesulitan dengan tugas programming atau butuh bantuan coding? KerjaKode siap membantu menyelesaikan tugas IT dan teknik informatika Anda. Dapatkan bantuan profesional di jasa tugas IT KerjaKode.
Ringkasan Teknik
Efek opposing direction yang dulu membutuhkan JavaScript kini bisa dibangun dengan tiga komponen utama:
view-timelinepada container untuk membuat timeline kustom@keyframesyang berbeda untuk masing-masing kolomanimation-rangeuntuk mengontrol kapan animasi aktif
Hasilnya adalah kode yang lebih ringan, tidak bergantung pada library eksternal, dan berjalan mulus di compositor thread tanpa membebankan main thread.
Selanjutnya, coba eksplorasi animation-range yang berbeda-beda untuk menciptakan choreografi yang lebih kompleks. Kombinasi cover, entry, dan exit memberi kamu kontrol sangat presisi atas timing setiap elemen di halaman.