will-change Özelliği Ne Zaman Yardımcı Olur, Ne Zaman Zararlı?
will-change özelliği tarayıcıya öğeyi önceden GPU katmanına almasını söyler; ancak her katman bellek tüketir. Hangi özelliklerde gerçek kazanç olduğu, neden animasyondan önce açıp sonra kapatmak gerektiği ve yaygın yanlış kullanımlar bu yazıda ele alınmaktadır.
will-change, CSS animasyon optimizasyonunda kulağa basit gelen bir özellik; tarayıcıya "bu öğe değişecek, hazır ol" deniyor. Dokümantasyon okununca her animasyona uygulanası geliyor, sonuçta GPU katmanı kazanmak demek. Pratikte görülen şey farklıdır: birkaç düzine öğeye toplu biçimde eklenmesi, bazı cihazlarda animasyonları iyileştirmek yerine yavaşlatır. Neden? Çünkü her katman bellek tüketir.
Sorun özelliğin kendisinde değil, maliyetini gözden kaçırmaktadır. will-change gerçekten işe yarar; ancak yalnızca doğru öğede, doğru zamanda. Bu koşulların dışındaki her kullanım GPU belleğini doldurur, tarayıcının işini kolaylaştırmak yerine zorlaştırır.
Aşağıda özelliğin ne zaman fark yarattığı, ne zaman yük oluşturduğu ve neden animasyondan önce açıp sonra kapatmak gerektiği somut biçimde ele alınmaktadır.
Tarayıcının render ardışık düzeni ve katmanlar
Tarayıcı bir sayfayı ekrana çizerken adımları sırayla işler: stil hesaplama, layout, paint (boyama), ardından compositing. Son adımda tarayıcı ayrı yüzeyleri üst üste birleştirerek nihai görüntüyü oluşturur. Bu yüzeylerin her birine compositing katmanı denir.
Çoğu öğe aynı katmanı paylaşır. Bir öğe animasyona girince tarayıcı onu kendi katmanına taşır, bu işleme "layer promotion" denir; sonra animasyon boyunca yalnızca o katmanı hareket ettirir ya da opaklığını değiştirir. CPU'ya dokunmadan, layout ve paint tetiklemeden GPU bu işi üstlenir. Animasyon pürüzsüzleşir.
will-change bu geçişi öne çeker. Öğe animasyona girmeden önce kendi katmanına alınır. Katman oluşturulurken ilk karede titreme olmaz, gecikme yaşanmaz. Kulağa kusursuz gelir; ama öğe kendi katmanında durduğu her an GPU belleği harcamaktadır.
Gerçek kazanç hangi özelliklerde olur?
will-change her CSS özelliği için eşit değerde değildir. GPU katmanında hesaplanabilen özellikler için anlamlı kazanç sağlar; bunların başında transform ve opacity gelir.
.panel {
will-change: transform;
}
transform ile yapılan öteleme ya da ölçekleme layout ve paint'i atlar, doğrudan GPU'da işlenir. opacity için de aynı mantık geçerlidir; opaklık değişince tarayıcı yeniden boyama yapmak zorunda kalmaz, katmanın alfa değeri güncellenir.
background-color, width, height, top, left farklı çalışır. Bu özellikler değişince layout ya da paint yeniden tetiklenir. will-change: background-color eklemek katman oluşturur, ancak bu temel maliyeti ortadan kaldırmaz. Sonuç: bellek gider, beklenen kazanç gelmez. Hangi CSS değişimlerinin layout'u tetiklediğini netleştirmek, will-change eklemekten önce gelir.
Compositable özellikler arasında filter de vardır; blur() veya brightness() animasyonları GPU katmanında işlenebilir, bu yüzden will-change: filter anlamlı bir seçenek olabilir. Ancak filtreler bazı durumlarda alt öğelerin stacking context'ini yeniden düzenler; davranışı DevTools ile gözlemlemeden uygulamak beklenmedik katman sorunları çıkarabilir. Kural aynıdır: özelliğin gerçekten compositable olup olmadığını anlamak, katman maliyetini üstlenmekten önce gelir.
Her katmanın bir bedeli var
Katman oluşturmak ücretsiz değildir. Bir öğe GPU katmanına taşındığında, o öğenin piksel boyutuna karşılık gelen bir bellek tamponu (buffer) ayrılır. 100x100 piksel küçük bir simge için bu önemsizdir. Viewport genişliğinde uzanan bir hero alanı için kayda değer yer kaplar.
Sayfada 50-100 öğeye toplu will-change uygulandığında masaüstü tarayıcılar bunu genellikle idare eder. Mobil cihazlar farklıdır. Orta segment Android telefonlarda GPU için ayrılan bellek sınırlıdır; fazla katman tarayıcıyı bellek baskısına sokar, tarayıcı da katmanları düşürüp yeniden oluşturmak zorunda kalır. Bu tam olarak önlenmek istenen titremeyi geri getirir.
Katman sayısı arttıkça compositing aşamasının kendisi de ağırlaşır. Tarayıcı her karede tüm katmanları birleştirmek zorundadır; bu birleştirme maliyeti katman sayısıyla doğru orantılı büyür. Kare hızının düşmesi buradaki baskıdan kaynaklanabilir. Gereksiz katmanlar da teknik borç gibi performansı anlık değil, kademeli olarak bozar.
will-change uygulaması aynı zamanda yeni bir stacking context oluşturur. Bu, öğenin z-index davranışını ve konumlandırılmış diğer öğelerle örtüşme sıralamasını değiştirebilir. Animasyon beklendiği gibi çalışırken overlay düzeninde açıklanamayan bir kayma görülüyorsa, yeni oluşan stacking context ilk kontrol edilecek noktadır; sorunu izole etmek için will-change'i geçici olarak kaldırmak yeterlidir.
Animasyondan önce açmak, sonra kapatmak
Statik bir öğenin kendi katmanında durması yalnızca bellek harcamaktadır. Doğru yaklaşım, animasyon başlamadan hemen önce will-change eklemek ve animasyon bitince kaldırmaktır.
CSS ile bunu :hover üzerinden sağlamak mümkündür:
.kart:hover {
will-change: transform;
}
Hover ile tetiklenen kısa geçişlerde bu yeterlidir. Tarayıcı hover'ı fark ettiği andan itibaren katman hazırlığına başlar; animasyon başladığında katman zaten hazırdır. Hover ile animasyonun eş zamanlı başladığı durumlarda avantaj azalır ama çoğu durumda yeterli bir aralık oluşur.
Daha kesin zamanlama için JavaScript kullanılır:
const el = document.querySelector('.animasyonlu');
el.addEventListener('mouseenter', () => {
el.style.willChange = 'transform';
});
el.addEventListener('animationend', () => {
el.style.willChange = 'auto';
});
animationend ya da transitionend olayı, katmanı tam zamanında serbest bırakır. auto değerine dönmek tarayıcıya "bu öğe artık öncelikli değil" mesajı verir, ayrılan GPU belleği serbest kalır. Uzun süreli ya da döngüsel animasyonlarda will-change'i açık bırakmak anlamlıdır; yine de sayfa yüklenirken değil, animasyon döngüsü başlarken eklenmesi tercih edilir.
Yanlış kullanımın yaygın biçimleri
Global bir seçiciyle uygulama en sık rastlanan hatadır:
* {
will-change: transform;
}
Ya da bir utility class'ın tüm animasyonlu bileşenlere otomatik atandığı yapılar. Bu, tarayıcıya sayfadaki her öğenin yakında değişeceğini bildirmek anlamına gelir. Tarayıcı hepsini promote eder, GPU belleği dolar, kare hızı düşer.
left ve top ile hareket ettirilen bir öğeye will-change: left, top eklenmesi de etkisizdir. Bu özellikler değişince layout tetiklenir; will-change bunu engellemez. Çözüm önce animasyonu transform: translate() kullanarak yeniden yazmak, ardından will-change: transform eklemektir.
Hiç hareket etmeyecek öğelere eklenmesi de sıkça görülür. header, footer ya da statik kart bileşenleri için will-change yalnızca bellek tüketir. Üretim öncesi kontrol listesine gereksiz katman promosyonlarını eklemek, bu tür hataları yayına çıkmadan yakalamayı kolaylaştırır.
translate3d(0,0,0) ile karşılaştırma
will-change yaygınlaşmadan önce GPU zorlamak için transform: translate3d(0, 0, 0) kullanılırdı. Bu teknik hâlâ çalışır; 3D transform tarayıcıyı katman oluşturmaya zorlar. Bellek maliyeti açısından ikisi arasında fark yoktur.
Aralarındaki asıl fark niyetin okunabilirliğidir. will-change: transform tarayıcıya doğrudan "bu öğede transform değişecek" der. translate3d(0, 0, 0) ise sıfır etkili bir 3D transform uygulamaktır; dolaylı ve bir tür hack'tir. Eski tarayıcı desteği gerekmiyorsa bu tekniği kullanmanın gerekçesi kalmamıştır. Bileşen kütüphanelerinden miras kalan animasyon kalıpları arasında bu tür eski pratikler bulunabilir; will-change ile ikame etmek kodu hem netleştirir hem de tarayıcıya daha doğru bir ipucu verir.
Karar vermek için pratik kontrol sırası
Hangi öğeye will-change ekleneceğine karar verirken dört soru işe yarar.
Animasyon transform ya da opacity kullanıyor mu? Hayır ise önce animasyonu bu özelliklere taşımak, sonra will-change değerlendirmek daha doğrudur. Layout ya da paint'i tetikleyen özellikler üzerinde katman oluşturmak maliyeti kaldırmaz.
Aynı anda kaç öğe animasyona giriyor? Birkaç öğe için yönetilebilir bir maliyet söz konusudur. Yüzlerce liste öğesi aynı anda animasyon yapıyorsa katman maliyeti kazancı geçebilir; bu durumda görüntü dışındaki öğelerin animasyonunu durdurmak daha etkili bir yaklaşımdır.
will-change ne kadar süre aktif kalacak? Kısa hover geçişleri için CSS yeterlidir. Uzun ya da programatik animasyonlar için JavaScript ile animationend zamanlama kontrolü güvenilirdir.
Sayfayı düşük güçlü bir cihazda test ettiniz mi? Masaüstünde pürüzsüz görünen animasyonlar mobil GPU belleği altında katman düşürmelerine neden olabilir. Hedefli müdahale, geniş kapsamlı uygulamadan neredeyse her zaman daha güvenlidir. Chrome DevTools'daki Layers paneli mevcut katmanları listeler; hangi öğenin katman aldığını ve piksel boyutunu görmek için kullanışlıdır. Rendering sekmesindeki "Layer borders" seçeneği de katman sınırlarını gerçek zamanlı olarak görselleştirir.
Bir animasyon kütüphanesi kullanıyorsanız kütüphanenin kendi içinde will-change ya da translate3d(0,0,0) zaten uyguladığını varsayın. Elle eklediğiniz değer o durumda gereksizdir; bazı tarayıcılar çakışan promote sinyallerini uyarı olarak kayıt altına alır. Kütüphanenin ürettiği satır içi stili DevTools Element panelinde incelemek, birkaç saniyede gereksiz katmanları ortaya çıkarır.
will-change, animasyonlarda ilk kare gecikmesini ve titreme sorunlarını gidermek için doğru bir araçtır. transform ya da opacity kullanan, birkaç belirli öğede, kısa süreliğine açılıp kapatılan biçimiyle belirgin kazanç sağlar. Bu koşullar dışında fayda sınırlıdır, maliyet gerçektir.
Açıp kapama disiplinini korumak birkaç satır JavaScript ya da bir :hover kuralıyla sağlanabilir. Katmanları DevTools'da düzenli olarak incelemek ise gereksiz promote edilmiş öğeleri birikmeden yakalar.
Her optimizasyon aracı gibi, will-change de ölçülmeden eklenmesi değil, somut bir sorunu çözmek için değerlendirilmesi gereken bir araçtır. Bir animasyonun sorunlu olduğunu gördünüz, katman promosyonunu test ettiniz, fark somutsa eklediniz; geri kalan her şeyde tarayıcıya güvenilir.