Scroll-driven Animation
Scroll-driven animation menghubungkan progress animasi dengan posisi scroll. Alih-alih berjalan berdasarkan waktu, animasi bergerak maju-mundur sesuai scroll pengguna.
Scroll Progress Indicator
Contoh paling klasik — progress bar yang menunjukkan berapa jauh user sudah scroll:
// JavaScript approach
window.addEventListener('scroll', () => {
const scrollTop = document.documentElement.scrollTop;
const scrollHeight = document.documentElement.scrollHeight;
const clientHeight = document.documentElement.clientHeight;
const progress = scrollTop / (scrollHeight - clientHeight);
document.querySelector('.progress-bar').style.transform =
\`scaleX(${progress})\`;
});
.progress-bar {
position: fixed;
top: 0;
left: 0;
right: 0;
height: 3px;
background: #3b82f6;
transform-origin: left;
transform: scaleX(0);
z-index: 50;
}
CSS scroll-timeline (Modern API)
Browser modern mendukung scroll-driven animation murni CSS — tanpa JavaScript!
/* Progress bar dengan pure CSS */
@keyframes grow-progress {
from { transform: scaleX(0); }
to { transform: scaleX(1); }
}
.progress-bar {
position: fixed;
top: 0;
left: 0;
right: 0;
height: 3px;
background: #3b82f6;
transform-origin: left;
animation: grow-progress auto linear;
animation-timeline: scroll();
}
⚠️ Dukungan browser terbatas:
animation-timeline: scroll()baru tersedia di Chrome/Edge 115+ (Juli 2023). Safari dan Firefox belum mendukung per April 2026. Kalau kamu targetkan pengguna umum, WAJIB sediakan fallback JavaScript. Cek support real-time di caniuse.com/css-scroll-timeline.
Fallback JavaScript (wajib untuk cross-browser)
Pola progressive enhancement: pakai CSS jika didukung, fallback ke JS kalau tidak.
// Deteksi dukungan
const supportsScrollTimeline = CSS.supports('animation-timeline: scroll()');
if (!supportsScrollTimeline) {
// Fallback pakai scroll event + rAF throttling
const progressBar = document.querySelector('.progress-bar');
let ticking = false;
window.addEventListener('scroll', () => {
if (!ticking) {
requestAnimationFrame(() => {
const scrolled = window.scrollY;
const max = document.documentElement.scrollHeight - window.innerHeight;
const progress = scrolled / max;
progressBar.style.transform = \`scaleX(${progress})\`;
ticking = false;
});
ticking = true;
}
}, { passive: true });
}
Kenapa requestAnimationFrame + flag ticking? Scroll event bisa fire ratusan kali per detik — tanpa throttling browser lag. rAF memastikan update max 60×/detik sinkron dengan refresh rate. Opsi { passive: true } memberi tahu browser kita tidak akan preventDefault(), jadi scroll tetap smooth.
Parallax Effect
Elemen bergerak dengan kecepatan berbeda saat scroll, menciptakan kedalaman:
window.addEventListener('scroll', () => {
const scrollY = window.scrollY;
// Layer belakang bergerak lambat
document.querySelector('.bg-layer').style.transform =
\`translateY(${scrollY * 0.3}px)\`;
// Layer depan bergerak normal (atau lebih cepat)
document.querySelector('.fg-layer').style.transform =
\`translateY(${scrollY * -0.1}px)\`;
});
Untuk performa lebih baik, gunakan rAF:
let ticking = false;
window.addEventListener('scroll', () => {
if (!ticking) {
requestAnimationFrame(() => {
updateParallax(window.scrollY);
ticking = false;
});
ticking = true;
}
});
function updateParallax(scrollY) {
document.querySelector('.hero-bg').style.transform =
\`translateY(${scrollY * 0.5}px)\`;
}
CSS Scroll-linked Animations
/* Fade in elemen saat di-scroll ke view */
@keyframes fade-in {
from {
opacity: 0;
transform: translateY(50px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.reveal-on-scroll {
animation: fade-in auto ease-out both;
animation-timeline: view();
animation-range: entry 0% entry 100%;
}
animation-range
Mengontrol kapan animasi mulai dan selesai relatif terhadap viewport:
.element {
animation: fade-in auto ease both;
animation-timeline: view();
/* Mulai saat elemen masuk viewport,
selesai saat 50% elemen terlihat */
animation-range: entry 0% entry 100%;
}
/* Atau: animasi sepanjang elemen terlihat */
.element-full {
animation: scale-up auto ease both;
animation-timeline: view();
animation-range: contain 0% contain 100%;
}
Contoh: Scroll-linked Header
let lastScroll = 0;
window.addEventListener('scroll', () => {
const currentScroll = window.scrollY;
const header = document.querySelector('.header');
// Scroll ke bawah — sembunyikan header
if (currentScroll > lastScroll && currentScroll > 100) {
header.style.transform = 'translateY(-100%)';
}
// Scroll ke atas — tampilkan header
else {
header.style.transform = 'translateY(0)';
}
// Tambah shadow saat sudah scroll
if (currentScroll > 10) {
header.classList.add('shadow');
} else {
header.classList.remove('shadow');
}
lastScroll = currentScroll;
});
.header {
position: fixed;
top: 0;
left: 0;
right: 0;
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.header.shadow {
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}
Performance Tips
- Throttle scroll events — gunakan rAF atau
passive: true - Hindari read-write cycle — baca semua dulu, baru tulis semua
- Gunakan
transform— jangan animasitop/left - CSS scroll-timeline — lebih performant karena berjalan di compositor thread
// Passive listener — browser tahu kamu tidak akan preventDefault
window.addEventListener('scroll', handler, { passive: true });
Scroll-driven animation adalah salah satu teknik paling impactful untuk membuat website terasa interaktif dan engaging.