İyi bir scroll animasyon kurgusu, sayfayı aşağı kaydırdıkça öğelerin tam zamanında belirmesini sağlar; ne erken patlar ne de geç kalır. Yıllarca bu işi scroll olayını dinleyip her kareyi elle hesaplayarak yaptık. Artık tarayıcı bu işi bizim için, ana iş parçacığını yormadan yapabiliyor: IntersectionObserver. Bu yazıda bir öğenin görünüme girip girmediğini verimli biçimde izlemeyi, bunu CSS ile birleştirip akıcı geçişler üretmeyi ve performans ile erişilebilirlik tuzaklarından kaçınmayı adım adım anlatıyorum.
Neden scroll olayı değil de Intersection Observer?
Klasik yöntemde window.addEventListener('scroll', ...) ile her kaydırma adımında her öğe için getBoundingClientRect() çağırırdık. İki sorun var: scroll olayı saniyede onlarca kez tetiklenir ve getBoundingClientRect() tarayıcıyı yeniden hesaplamaya (layout/reflow) zorlar. Bu kombinasyon ana iş parçacığını tıkar, kaydırma takılır.
Intersection Observer ise tersine çalışır: sen tarayıcıya "şu öğe görünüme girince bana haber ver" dersin, tarayıcı bunu kendi içinde, asenkron ve optimize edilmiş şekilde izler. Geri çağrı yalnızca kesişim durumu değiştiğinde çalışır, her kaydırma karesinde değil. Sonuç: daha az kod, daha az reflow, daha akıcı sayfa.
İlk gözlemciyi kurmak
Temel kullanım üç parçadan oluşur: gözlemlenecek hedef öğeler, kesişim olunca çalışacak geri çağrı ve davranışı ayarlayan seçenekler.
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
entry.target.classList.add('is-visible');
// bir kez göründükten sonra izlemeyi bırak
observer.unobserve(entry.target);
}
});
}, {
threshold: 0.2, // öğenin %20'si görününce tetikle
rootMargin: '0px 0px -10% 0px'
});
document.querySelectorAll('.reveal').forEach((el) => {
observer.observe(el);
});
Burada işin püf noktası şu: JavaScript yalnızca bir CSS sınıfı (is-visible) ekliyor. Asıl animasyonu CSS yapıyor. Bu ayrım hem performans hem de bakım açısından kritik.
Animasyonu CSS'e bırakmak
Öğenin başlangıç ve bitiş durumunu CSS'te tanımlayıp aradaki geçişi transition ile yumuşatıyoruz. transform ve opacity kullanmak önemli: bu iki özellik GPU üzerinde işlenir ve düzen (layout) yeniden hesaplaması tetiklemez.
.reveal {
opacity: 0;
transform: translateY(24px);
transition: opacity 0.6s ease, transform 0.6s ease;
}
.reveal.is-visible {
opacity: 1;
transform: translateY(0);
}
Aynı yapıyla soldan kayma, ölçeklenme veya hafif dönme gibi varyasyonları farklı sınıflarla üretebilirsin. Animasyonu CSS'te tutmanın bir başka avantajı: JavaScript çalışmasa bile içerik opacity: 0 ile gizli kalmaz; ilerleyen "erişilebilirlik" bölümünde bunu güvene alacağız.
threshold ve rootMargin'i doğru ayarlamak
Bu iki seçenek scroll animasyon hissini tümüyle belirler:
- threshold: 0 ile 1 arası bir oran.
0öğenin tek pikseli görününce,1ise tamamı görününce tetikler. Bir dizi de verebilirsin ([0, 0.25, 0.5, 1]) ve her eşikte geri çağrıyı alırsın. - rootMargin: Kök çerçevesini büyütüp küçültür; CSS
marginsözdizimini kullanır.-10%gibi negatif bir alt değer, öğe ekranın biraz içine girmeden tetiklemeyi geciktirir, böylece animasyon "tam zamanında" hissedilir. - root: Varsayılan olarak görüntü alanıdır (
null). Kaydırılabilir bir kapsayıcı içinde izleme yapacaksan o öğeyirootolarak verirsin.
Kademeli (stagger) giriş efekti
Kartların art arda, hafif gecikmeyle belirmesi modern arayüzlerde çok sevilir. Bunu her öğeye CSS değişkeni ile gecikme atayarak temiz biçimde yapabilirsin:
const items = document.querySelectorAll('.reveal');
const io = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (!entry.isIntersecting) return;
const i = Number(entry.target.dataset.index || 0);
entry.target.style.setProperty('--delay', `${i * 80}ms`);
entry.target.classList.add('is-visible');
io.unobserve(entry.target);
});
}, { threshold: 0.15 });
items.forEach((el, i) => {
el.dataset.index = i;
io.observe(el);
});
CSS tarafında geçişe bu gecikmeyi bağlarsın: transition-delay: var(--delay, 0ms);. Böylece dört kart sırayla, akıcı bir dalga halinde belirir.
Performans ve erişilebilirlik tuzakları
Birkaç noktaya dikkat etmek, projeyi profesyonel kılar:
- Tekrar tetiklemeyi yönet: Öğe bir kez göründükten sonra
unobserve()ile izlemeyi bırak; aksi halde kullanıcı yukarı kaydırınca animasyon sıfırlanır. Tekrar oynamasını istiyorsanis-visiblesınıfını!entry.isIntersectingdurumunda kaldır. - Hareket azaltma tercihi: Bazı kullanıcılar hareketten rahatsız olur.
@media (prefers-reduced-motion: reduce)ile geçişleri kapat ve içeriği doğrudan görünür yap. - JavaScript kapalıysa içerik gizli kalmasın: Başlangıç gizliliğini yalnızca JS aktifken uygula. Pratik bir yol:
<html>kök öğesinedocument.documentElement.classList.add('js')ekleyip CSS'i.js .reveal { opacity: 0; }şeklinde koşullamaktır.
@media (prefers-reduced-motion: reduce) {
.reveal {
opacity: 1;
transform: none;
transition: none;
}
}
Sık Sorulan Sorular
Intersection Observer tarayıcı desteği nasıl?
Tüm güncel tarayıcılarda (Chrome, Firefox, Safari, Edge) yıllardır destekleniyor; ayrı bir kütüphaneye gerek yok. Çok eski tarayıcıları hedeflemiyorsan polyfill'e de ihtiyacın olmaz.
requestAnimationFrame ile scroll dinlemek daha mı iyi?
Sürekli ilerleyen (scroll boyunca değişen) parallax gibi efektler için requestAnimationFrame uygundur. Ama bir öğenin görünüp görünmediğini bilmek için Intersection Observer her zaman daha verimli ve sade çözümdür.
Animasyon her kaydırışta tekrar oynamalı mı?
Genelde hayır. İçerik bir kez belirsin ve öyle kalsın; sürekli tekrar eden giriş animasyonları dikkat dağıtır ve performansı yorar. İstisnai durumlarda tekrar oynatabilirsin ama varsayılan olarak unobserve() önerilir.
Sitende akıcı, performanslı scroll animasyonları mı istiyorsun? Doğru eşiklerle ayarlanmış, erişilebilir ve takılmayan arayüz efektleri için benimle iletişime geç.