CSS Animations ve Transitions: GPU Katmanı Farkı Nedir?
transform ve opacity neden compositor thread'de çalışır, left ve background-color neden her karede layout veya paint tetikler? GPU katmanı avantajı ve will-change kullanımındaki gerçek değiş tokuş.
CSS animasyonları ve geçişler (transitions), bir arayüzde kullanıcının dikkatini yönlendirmenin ve etkileşimi geri bildirmenin en doğrudan yollarından biridir. Ama aynı görsel sonucu veren iki farklı CSS kuralı, tarayıcı içinde tamamen farklı bir işlem yükü yaratabilir. Birincisi yalnızca compositing aşamasını tetikler; ikincisi her kare için layout ve paint döngüsünü yeniden başlatır. Bu fark, akıcı 60 fps ile takılan bir animasyon arasındaki çizgidir.
Sorun yalnızca karmaşık animasyonlarda ortaya çıkmaz. Basit bir hover geçişi bile yanlış özelliği canlandırınca ana iş parçacığını (main thread) meşgul eder; kullanıcı etkileşimlerine yanıt gecikmesiyle sonuçlanır. Hangi özelliğin nerede çalıştığını bilmek, performans optimizasyonunu sezgiden çıkarır ve ölçülebilir bir karara dönüştürür.
Bunu anlamak için tarayıcının bir kareyi nasıl oluşturduğuna bakmak gerekir. Render hattının üç kritik aşaması arasındaki ilişki, GPU katmanı kararının temelidir.
Tarayıcı Render Hattı: Layout, Paint, Composite
Tarayıcı bir kareyi ekrana getirirken üç aşamadan geçer: layout (düzen hesaplama), paint (piksel çizimi) ve composite (katmanları birleştirme). Her aşama bir öncekini tetikleyebilir; ancak zorunlu değildir.
Layout aşamasında tarayıcı, her elemanın boyutunu ve konumunu hesaplar. Bu hesaplama DOM'daki bağımlılık zincirine bakarak çalışır. Bir elemanın width, height, margin, padding, top, left değerleri değişince tarayıcının etkilenen tüm elemanlar için bu hesabı yeniden yapması gerekir. Büyük bir DOM ağacında yeniden hesaplama kümülatif bir maliyet oluşturur.
Paint aşamasında tarayıcı, layout bilgisini piksele çevirir. Renkler, kenarlıklar, gölgeler ve arka planlar bu aşamada işlenir. Layout tetiklenmeden paint tek başına çalışabilir; ancak paint tetiklendiğinde composite de tetiklenir. Composite aşamasında ise önceki aşamalarda oluşturulan katmanlar GPU yardımıyla birleştirilir ve ekrana gönderilir. Eğer layout ve paint atlanabilirse, composite işlemi compositor thread adı verilen ayrı bir iş parçacığında, main thread'i hiç meşgul etmeden gerçekleşebilir.
Kullanıcı sayfayı kaydırırken veya bir animasyon çalışırken main thread'in meşgul olmaması kritik önem taşır. Bir JavaScript işlemi main thread'i doldurduğunda, compositor thread üzerinde çalışan animasyon etkilenmez; ama layout/paint gerektiren bir animasyon o anda durur.
Compositor Thread: Main Thread'den Bağımsız Çalışma
Compositor thread, compositing görevlerini ana iş parçacığından bağımsız olarak yürütür. Bu ayrımın pratik anlamı şudur: main thread bir JavaScript işlemiyle veya DOM hesaplamasıyla meşgulken compositor thread animasyonu sürdürmeye devam edebilir.
Ancak compositor thread'in devreye girebilmesi için iki koşul gereklidir. Birincisi, animasyona konu olan özellik yalnızca compositing aşamasını etkilemelidir. İkincisi, ilgili elemanın kendi katmanında (layer) olması gerekir. Bu iki koşul sağlandığında tarayıcı animasyonu GPU'ya devreder ve main thread'i devre dışı bırakır.
Katman oluşturma işlemi otomatik veya el ile tetiklenebilir. Tarayıcı bazı elemanları zaten ayrı katmanlara koyar; <video>, <canvas> ve position: fixed elemanları bunun örnekleridir. will-change: transform veya transform: translateZ(0) gibi bir kural eklendiğinde ise tarayıcıya "bu eleman için şimdiden ayrı katman oluştur" ipucu verilmiş olur.
transform ve opacity: GPU'nun Özel Vatandaşları
CSS özelliklerinin büyük çoğunluğu layout veya paint aşamasını tetikler. Ancak transform ve opacity, compositor thread'de çalışabilen iki özellik olarak ayrı bir konumda durur.
transform özelliği, bir elemanı piksel düzeyinde kaydırır, döndürür veya ölçeklendirir. Önemli olan nokta: bu işlem elemanın yerini DOM akışında değiştirmez, diğer elemanların konumunu etkilemez. Tarayıcı sadece mevcut katmanı GPU'da başka bir konuma yerleştirir. opacity değişimi de benzer şekilde çalışır; bir elemanın saydamlığını değiştirmek ne boyutunu ne konumunu ne de çevresindeki elemanları etkiler.
Her ikisi de yalnızca zaten var olan piksel verisiyle çalışır. Bu yüzden layout ve paint atlanır, işlem doğrudan composite'e gider. Bu iki özelliğin dışında GPU avantajından yararlanmak için genellikle onların üzerinden geçmek gerekir: konum değişimi için left/top yerine translateX/translateY kullanımı, görünürlük değişimi için display: none yerine opacity geçişi bunların başında gelir.
Pratikte bu şu anlama gelir: bir kart üzerine gelindiğinde gölge büyüten bir animasyon box-shadow değerini değiştiriyorsa her karede paint tetikler. Aynı efekt bir pseudo-element gölgesiyle yapılıp opacity geçişiyle gösterilirse yalnızca composite aşamasını kullanır. Görsel sonuç benzer, işlem yükü farklıdır.
Hangi Özellikler Layout, Hangisi Paint Tetikler?
CSS özelliklerini tetikledikleri aşamaya göre üç gruba ayırmak, animasyon kararlarını hızla netleştirir.
Layout ve paint tetikleyen özellikler: width, height, margin, padding, border-width, top, left, right, bottom, font-size, display, position. Bu özelliklerden herhangi birinin animasyonu tarayıcıyı her kare için sıfırdan hesaplamaya zorlar.
Yalnızca paint tetikleyen özellikler: background-color, color, border-color, box-shadow, outline. Bunlar layout aşamasını tetiklemez; ancak paint gerektirir. Layout hesaplamasından kurtulunmuş olsa da GPU katmanı avantajından yararlanılamaz.
Yalnızca composite kullanan özellikler: transform, opacity, ve sınırlı koşullarda filter. Bu grup main thread'i meşgul etmeden compositor thread'de çalışabilen özellikler içerir.
Bir animasyonun hangi gruba girdiğini tarayıcının DevTools araçlarından doğrulamak mümkündür. Chrome'un Performance panelinde "Rendering" sekmesinde "Paint flashing" etkinleştirildiğinde hangi elemanların her karede yeniden boyandığı görülür. Yeşil çerçeve paint tetiklendiğine, kırmızı çerçeve layout aşamasının devreye girdiğine işaret eder. Çok sayıda eleman içeren arayüzlerde layout tetikleyen bir animasyonun etkisi katlanarak büyür; çok bileşenli uygulamalarda render maliyetini azaltmak için bu bağımlılık zincirleri ayrıca incelenmeyi hak eder.
will-change: Ne Zaman Yardımcı Olur, Ne Zaman Zarar Verir?
will-change özelliği tarayıcıya bir ipucu verir: "bu elemanın belirtilen özelliği yakında değişecek, şimdiden hazır ol." Tarayıcı bu ipucunu alınca elemanı ayrı bir katmana alır ve animasyon başlamadan önce GPU belleğine yükler.
.kart {
will-change: transform;
}
Bu yaklaşım animasyon başladığı anda katman oluşturma gecikmesini önler. Kaydırma tabanlı parallax efektlerinde veya hover durumunda tetiklenen geçişlerde ilk karedeki takılmayı ortadan kaldırır. Yalnız bir koşulda işe yarar: animasyon gerçekten çalışacak ve GPU katmanı gerçekten kullanılacaksa.
Çok fazla elemana will-change uygulandığında GPU belleği aşırı yüklenir. Her ön hazırlık yapılmış katman GPU RAM'inde yer tutar. Düşük bellekli mobil cihazlarda bu durum animasyonsuz bir sayfanın daha akıcı görünmesine yol açabilir; avantaj tersine döner.
will-change: transform uygulanmış bir eleman, overflow: hidden olan ata elemanından taşabilir. Katman bağımsızlığı stacking context'i değiştirir; z-index beklenmedik davranışlar üretebilir. Animasyon bitmeden önce statik her elemana toplu eklemek yerine, animasyon öncesinde JavaScript ile eklenip animasyon bittikten sonra kaldırılacak şekilde kullanmak daha doğru bir yaklaşımdır.
CSS animation ile CSS transition: GPU Kullanımı Açısından Fark
@keyframes ile tanımlanan CSS animasyonları ve transition özelliğiyle tanımlanan geçişler, çalışma mekanizması açısından büyük ölçüde benzer davranır. Her ikisi de compositor thread'den yararlanabilir; koşul aynıdır: animasyona konu olan özellik compositor aşamasında işlenebilir olmalıdır.
Fark animasyonun kontrolünde yatar. @keyframes animasyonları tarayıcı tarafından tamamen yönetilir; JavaScript müdahalesi olmadan başlar ve biter. Bu nedenle main thread meşgul olsa bile çoğu durumda compositor thread animasyonu sürdürür. transition ise bir durum değişikliğine bağlıdır; genellikle JavaScript veya pseudo-class (:hover, :focus) değişimiyle tetiklenir. Tetikleyici main thread üzerindeyse geçişin başlangıcı ana iş parçacığına bağlıdır; ama bir kez başladıktan sonra compositor thread devralabilir.
Web Animations API aracılığıyla çalışan JavaScript animasyonları da aynı değerlendirmeye tabidir. element.animate() ile oluşturulan transform ve opacity animasyonları compositor thread'e devredilebilir. Ancak her karede JavaScript'te değer hesaplayıp style değiştiren bir döngü bu avantajı yok eder. Araç değil, özellik belirler. @keyframes kullansanız da transition kullansanız da left'i canlandırıyorsanız layout tetiklemeye devam edersiniz. Yayına çıkmadan önce yapılan performans kontrollerinde animasyon özellik seçimi bu yüzden ayrı bir madde olarak yer alır.
Katman Sayısı ve Bellek: Dengeyi Nerede Kurmak Gerekir?
GPU katmanları bir çözüm değil, bir araçtır. Her katman GPU belleğinde yer kaplar; ekran çözünürlüğü, içerik boyutu ve saydam piksel miktarıyla birlikte bu alan büyür. 1x ekranda 400x400 piksel bir eleman için ayrılan bellek, 3x retina ekranda dokuz kata çıkabilir.
Katman promosyonu (layer promotion) kararı gerçek bir değiş tokuştur. Animasyon olmayan veya nadiren değişen elemanlar için GPU katmanı açmak net bir maliyet yaratır. Tarayıcı zaten gereken durumlarda katman açar; manuel tetikleyici yalnızca tarayıcının öngöremediği durumlarda anlam taşır. Büyük bir arka plan görseli veya video üzerinde çalışan bir animasyon, katman olmadan her karede tüm kompozisyonu yeniden yapacaktır; bu durumda katman açmak net kazanç sağlar.
Tarayıcının oluşturduğu katmanları görmek için Chrome DevTools içindeki "Layers" paneli kullanılabilir. Panel, her katmanın bellekte kapladığı alanı ve oluşturulma nedenini gösterir. Gereksiz katmanların büyük çoğunluğu burada kolayca fark edilir.
Tasarım sistemleri animasyon kararlarını da etkiler: birden fazla bileşende aynı geçiş değerleri merkezi olarak tanımlandığında, tek bir hatalı özellik seçimi tüm sisteme yayılır. Bileşen kütüphanelerinde transform ve opacity dışına çıkan animasyon tanımlarını erken yakalamak bu yüzden önemlidir. Teknik borç performansı yavaş yavaş bozarken animasyon seçimleri de birikerek etki eder; tek bir elemanda left animasyonu fark edilmez, aynı kalıp on bileşene yayıldığında düşük cihazlarda gözle görülür bir yavaşlamaya neden olur.
GPU katmanı avantajından yararlanmak için karmaşık bir kurulum gerekmez. transform ve opacity kullanmak, left/top/background-color animasyonlarından kaçınmak ve will-change'i yalnızca gerçekten animasyon çalışacak elemanlara uygulamak çoğu durumda yeterli bir başlangıçtır. Kalan kararlar DevTools çıktısına dayanarak alınmalıdır.
Animasyonlardan önce render hattını anlamak önemli bir fark yaratır. Tarayıcının hangi aşamada ne iş yaptığını bilince "neden takılıyor?" sorusuna çoğunlukla CSS kuralı okuyarak yanıt verilebilir hale gelir. Ölçüm alışkanlığı sezgiye güvenmekten daha güvenilir bir yol sunar; DevTools profil çıktısı hangi animasyonun layout zincirine girdiğini milisaniye düzeyinde açık eder.
Pratikte en sık yapılan hata animasyon tekniğini değiştirmek yerine, canlandırılan özelliği değiştirmemektir. Performans kararları çoğunlukla yapısal seçimlerle başlar; ancak render hattını bypass etmek bazen tek bir CSS satırına indirgenir.