Intersection Observer untuk Animasi
IntersectionObserver API memungkinkan kamu mendeteksi kapan elemen masuk atau keluar dari viewport (atau container tertentu). Ini sangat berguna untuk scroll-triggered animation, lazy loading, dan infinite scroll.
Membuat Intersection Observer
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
// Elemen terlihat di viewport
entry.target.classList.add('visible');
}
});
});
// Observe elemen
document.querySelectorAll('.animate-on-scroll').forEach((el) => {
observer.observe(el);
});
Options
const observer = new IntersectionObserver(callback, {
// root — container yang jadi acuan (default: viewport)
root: null,
// rootMargin — margin di sekitar root
// Positif = trigger lebih awal, negatif = trigger lebih lambat
rootMargin: '0px 0px -100px 0px',
// threshold — berapa persen elemen harus terlihat
// 0 = baru masuk sedikit, 1 = harus full visible
threshold: 0.1,
});
Threshold
// Trigger saat 10% elemen terlihat
new IntersectionObserver(cb, { threshold: 0.1 });
// Trigger saat 50% elemen terlihat
new IntersectionObserver(cb, { threshold: 0.5 });
// Trigger saat 100% elemen terlihat
new IntersectionObserver(cb, { threshold: 1.0 });
// Multiple thresholds — callback dipanggil di setiap threshold
new IntersectionObserver(cb, {
threshold: [0, 0.25, 0.5, 0.75, 1.0]
});
rootMargin
// Trigger 200px SEBELUM elemen masuk viewport (preload)
new IntersectionObserver(cb, {
rootMargin: '200px 0px'
});
// Trigger SETELAH elemen 100px di dalam viewport
new IntersectionObserver(cb, {
rootMargin: '-100px 0px'
});
Scroll-triggered Animation Pattern
/* State awal — belum terlihat */
.animate-on-scroll {
opacity: 0;
transform: translateY(30px);
transition: opacity 0.6s ease-out, transform 0.6s ease-out;
}
/* State akhir — terlihat */
.animate-on-scroll.visible {
opacity: 1;
transform: translateY(0);
}
/* Variasi arah */
.from-left {
transform: translateX(-50px);
}
.from-left.visible {
transform: translateX(0);
}
.from-right {
transform: translateX(50px);
}
.from-right.visible {
transform: translateX(0);
}
.scale-in {
transform: scale(0.8);
}
.scale-in.visible {
transform: scale(1);
}
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
entry.target.classList.add('visible');
// Unobserve setelah animasi (opsional)
observer.unobserve(entry.target);
}
});
}, {
threshold: 0.1,
rootMargin: '0px 0px -50px 0px',
});
document.querySelectorAll('.animate-on-scroll').forEach((el) => {
observer.observe(el);
});
Stagger Animation
Beri delay berbeda untuk setiap elemen agar muncul berurutan:
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
// Ambil index dari data attribute
const delay = entry.target.dataset.delay || 0;
entry.target.style.transitionDelay = \`${delay}ms\`;
entry.target.classList.add('visible');
observer.unobserve(entry.target);
}
});
}, { threshold: 0.1 });
// HTML: <div class="animate-on-scroll" data-delay="0">
// <div class="animate-on-scroll" data-delay="100">
// <div class="animate-on-scroll" data-delay="200">
Lazy Loading Images
const imageObserver = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
const img = entry.target;
// Pindahkan src dari data-src ke src
img.src = img.dataset.src;
img.classList.add('loaded');
imageObserver.unobserve(img);
}
});
}, {
rootMargin: '200px 0px', // Load 200px sebelum terlihat
});
document.querySelectorAll('img[data-src]').forEach((img) => {
imageObserver.observe(img);
});
Cleanup
Jangan lupa disconnect observer saat tidak dibutuhkan lagi:
// Stop observing satu elemen
observer.unobserve(element);
// Stop observing semua elemen
observer.disconnect();
Intersection Observer adalah tool yang powerful dan performant karena callback-nya berjalan secara asynchronous dan tidak memblok main thread, berbeda dengan scroll event listener.