Daha iyi oluşturma performansı için Jank bozma

Tom Wiltzius
Tom Wiltzius

Giriş

Animasyonlar, geçişler ve diğer küçük kullanıcı arayüzü efektlerini yaparken web uygulamanızın duyarlı ve sorunsuz olmasını istersiniz. Bu efektlerin zararsız olmasını sağlamak, "yerel" ile örtüşme arasındaki farkı ifade edebilir. görünmesidir.

Bu, tarayıcıda oluşturma performansı optimizasyonunu ele alan bir dizi makalenin ilkidir. İlk olarak, akıcı animasyonun neden zor olduğunu ve bunu başarmak için ne yapılması gerektiğini ele alacağız. Ayrıca, birkaç kolay en iyi uygulamaya değineceğiz. Bu fikirlerin çoğu ilk başta "Jank Busters"ta bu yıl Google I/O konuşmasında (video) Nat Duca ile konuştuk.

V-sync ile tanışın

PC oyuncuları bu terime aşina olabilir ancak web'de pek yaygın değildir: v-sync nedir?

Telefonunuzun ekranını düşünün: Düzenli aralıklarla, genellikle (ancak her zaman değil!) saniyede yaklaşık 60 kez yenilenir. V-sync (veya dikey senkronizasyon), yalnızca ekran yenilemeleri arasında yeni kareler oluşturma yaklaşımını ifade eder. Bunu ekran arabelleğine veri yazan süreç ile işletim sisteminin bu verileri ekrana koymak için okuması arasındaki bir yarış durumu gibi düşünebilirsiniz. Arabelleğe alınmış çerçeve içeriğinin bu yenilemeler sırasında değil, bu yenilemeler arasında değişmesini istiyoruz; Aksi takdirde, monitör bir karenin yarısını diğerinin yarısını görüntüler ve bu da "ayırma" sonucuna yol açar.

Akıcı bir animasyon için ekran her yenileme gerçekleştiğinde yeni bir karenin hazır olması gerekir. Bunun iki önemli etkisi vardır: kare zamanlaması (yani, karenin hazır olması gerektiğinde) ve kare bütçesi (yani tarayıcının bir kare oluşturması için gereken süre). Bir kareyi tamamlamak için yalnızca ekran yenilemeleri arasında geçen süre vardır (60 Hz'lik ekranda yaklaşık 16 ms) ve son kare ekrana yüklendiği anda bir sonraki kareyi üretmeye başlamak istiyorsunuz.

Zamanlama Her Şeydir: requestAnimationFrame

Birçok web geliştiricisi, animasyon oluşturmak için her 16 milisaniyede bir setInterval veya setTimeout kullanır. Bu, çeşitli nedenlerden dolayı ortaya çıkan bir sorundur (birazdan bu konuyu daha ayrıntılı olarak ele alacağız), ancak özellikle endişe duyduğunuz konular şunlardır:

  • JavaScript'teki zamanlayıcıların çözümlemesi yalnızca birkaç milisaniyelik bir sıçramadadır
  • Farklı cihazların yenileme hızları farklıdır

Yukarıda belirtilen kare zamanlaması sorununu hatırlayın: Bir sonraki ekran yenileme işlemi gerçekleşmeden önce hazır olmak için tüm JavaScript, DOM değiştirme, düzen, boyama vb. ile tamamlanmış bir animasyon çerçevesinin olması gerekir. Düşük zamanlayıcı çözünürlüğü, animasyon karelerinin bir sonraki ekran yenilemesinden önce tamamlanmasını zorlaştırabilir, ancak ekran yenileme hızlarındaki değişim, sabit bir zamanlayıcıyla bunu imkansız hale getirir. Zamanlayıcı aralığı ne olursa olsun bir karenin zamanlama aralığından yavaşça çıkar ve bir kare bırakırsınız. Zamanlayıcı milisaniyelik doğrulukta etkinleşse bile (geliştiricilerin keşfettiği gibi) bu durum ortaya çıkabilir. Zamanlayıcının çözünürlüğü, makinenin pille mi yoksa fişe mi takılı mı olduğuna bağlı olarak değişir. Bu durum nadiren arka plan sekmelerinin aşırı kullanımından etkilenebilir (örneğin, bir milisaniye gözüktüğü için 16 karede bir) ve saniyede birkaç kare atlıyorsunuz. Aynı zamanda hiçbir zaman görüntülenmeyen kareler oluşturmak için gerekli çalışmaları yapacaksınız. Bu da uygulamanızda başka şeyler yapmak için harcayabileceğiniz güç ve CPU süresini boşa harcayacak.

Farklı ekranlarda farklı yenileme hızları kullanılır: 60 Hz yaygın olarak kullanılır, ancak bazı telefonlarda 59 Hz, bazı dizüstü bilgisayarlarda düşük güç modunda 50 Hz'e, bazı masaüstü monitörlerde ise 70 Hz'lik değerler kullanılır.

Oluşturma performansından bahsederken genellikle saniyedeki kare sayısı (FPS) metriğine odaklanırız ancak varyans daha büyük bir sorun olabilir. Gözlerimiz, animasyonda zamanlaması iyi olmayan bir animasyonun oluşturabileceği ufak, düzensiz aksaklıkları fark ediyor.

Animasyon karelerini doğru şekilde zamanlamanın yolu requestAnimationFrame kullanmaktır. Bu API'yi kullandığınızda tarayıcıdan bir animasyon çerçevesi istersiniz. Tarayıcı kısa bir süre içinde yeni bir çerçeve oluşturacak olduğunda geri çağırmanız yapılır. Bu durum, yenileme hızı ne olursa olsun gerçekleşir.

requestAnimationFrame başka güzel özelliklere de sahip:

  • Arka plan sekmelerindeki animasyonlar duraklatılarak sistem kaynakları ve pil ömrü korunur.
  • Sistem, oluşturmayı ekranın yenileme hızında gerçekleştiremezse animasyonları kısıtlayabilir ve geri çağırmayı daha seyrek üretebilir (örneğin, 60 Hz'lik bir ekranda saniyede 30 kez). Bu, kare hızını yarıya düşürse de animasyonun tutarlı olmasını sağlar. Yukarıda da belirtildiği gibi, gözlerimiz kare hızından çok varyansa çok daha uygundur. Sabit bir 30 Hz, saniyede birkaç kareyi atlayan 60 Hz'den daha iyi görünür.

requestAnimationFrame daha önce her yerde tartışıldı. Bu nedenle, daha fazla bilgi için Creative JS'deki bu makale gibi makalelere göz atın. Ancak bu, animasyonun sorunsuz olması için önemli bir ilk adımdır.

Çerçeve Bütçesi

Her ekran yenilemesinde yeni bir karenin hazır olmasını istediğimizden, yeni bir kare oluşturmak üzere tüm işleri yapabilmek için yenilemeler arasında geçen süre vardır. 60 Hz'lik bir ekranda bu, tüm JavaScript'i çalıştırmak, düzeni gerçekleştirmek, boyamak ve çerçeveyi çıkarmak için tarayıcının yapması gereken diğer işlemleri yapmak için yaklaşık 16 ms'miz olduğu anlamına gelir. Bu, requestAnimationFrame geri çağırmanızdaki JavaScript'in çalışması 16 ms'den uzun sürerse v-sync için zamanında bir kare oluşturma umudunuzun olmadığı anlamına gelir.

16 ms çok uzun bir süre değil. Neyse ki Chrome'un Geliştirici Araçları, requestAnimationFrame geri çağırması sırasında kare bütçenizi tükettiğinizde bunu takip etmenize yardımcı olabilir.

Geliştirici Araçları zaman çizelgesini açmak ve bu animasyonun çalışırken kaydını almak, animasyon oluştururken bütçenin aşıldığını gösterir. Zaman Çizelgesi'nde "Kareler"e geç göz atın:

Çok fazla düzen içeren bir demo
Çok fazla sayfa düzeni içeren bir demo

Bu requestAnimationFrame (rAF) geri çağırmaları 200 ms'den uzun sürüyor. Bu, 16 ms'de bir kare seçmek için çok uzun bir büyüklük sırasıdır. Bu uzun rAF geri çağırmalarından birini açtığınızda içeride neler olup bittiğini ortaya koyuyor: Bu örnekte, çok sayıda sayfa düzenleniyor.

Polat'ın videosunda, geçişin özel nedeni (scrollTop okunuyor) ve bundan nasıl kaçınılacağına daha ayrıntılı bir şekilde değiniliyor. Ancak burada önemli olan, sizi geri çağırmak ve neyin bu kadar uzun sürdüğünü araştırmaktır.

Çok azaltılmış düzene sahip güncellenmiş bir demo
Çok küçültülmüş düzene sahip güncellenmiş bir demo

16 ms kare sürelerine dikkat edin. Çerçevelerdeki bu boş alan, daha fazla iş yapmak (veya tarayıcının arka planda çalışmasını sağlamak) için sahip olmanız gereken bir fırsattır. Bu boş alan İyi Bir Şeydir.

Diğer Jank Kaynağı

JavaScript destekli animasyonları çalıştırmaya çalışırken yaşanan en büyük sorun başka şeylerin rAF geri çağırmanıza engel olabileceği ve hatta bunu çalışmasını engeller. rAF geri çağırmanız yalın ve birkaç dakika içinde yapılmış olsa bile milisaniye cinsinden, diğer etkinlikler (yeni gelen bir XHR giriş etkinliği işleyicilerini çalıştırmak veya bir zamanlayıcıda planlanmış güncellemeleri çalıştırmak gibi) bir süre verim almadan bir süre aniden gelip çalışmayacaktır. Mobil cihazda cihazların bazen bu etkinlikleri işlemesi yüzlerce milisaniye sürebilir. bu süreçte animasyonunuz tamamen durur. Bunlara animasyonda oluşan aksaklıklar jank'tır.

Bu tür durumlardan kaçınmanız mümkün olmasa da mimariyle ilgili olarak başarıya ulaşmanızı sağlayacak birkaç en iyi uygulama vardır:

  • Giriş işleyicilerde çok fazla işlem yapmayın. Örneğin, çok fazla JS işlemi yaparak veya tüm sayfayı yeniden düzenlemeye çalışıyor olabilirsiniz. onscroll işleyici, korkunç acayipliklerin en yaygın nedenlerinden biridir.
  • rAF geri çağırmanıza veya Web Çalışanlarınıza mümkün olduğunca fazla işlem (okuma: çalışması uzun sürecek her şey) aktarın.
  • Çalışmayı rAF geri çağırmasına iterseniz, her karenin yalnızca bir kısmını işleyecek şekilde parçalara ayırmaya çalışın veya önemli bir animasyon bitene kadar geciktirin. Bu şekilde kısa rAF geri çağırmaları çalıştırmaya devam edebilir ve sorunsuz animasyon uygulayabilirsiniz.

İşlemenin, giriş işleyiciler yerine requestAnimationFrame geri çağırmalarına nasıl aktarılacağını anlatan harika bir eğitim için Paul Lewis'in Leaner, Meaner, Faster Animations with requestAnimationFrame (Leaner, Meaner, Faster Animations with requestAnimationFrame) makalesine bakın.

CSS Animasyonu

Etkinliğinizdeki basit JS ve rAF geri çağırmalarından daha iyidir? JS yok.

Daha önce, rAF geri çağırmalarının kesintiye uğramasını engelleyen bir sihirli değnek olmadığını söylemiştik. Ancak bunlara ihtiyaç duymamak için CSS animasyonunu kullanabilirsiniz. Özellikle Android için Chrome'da (ve diğer tarayıcılar benzer özellikler üzerinde çalışırken) CSS animasyonları, JavaScript çalışıyor olsa bile tarayıcının bunları çalıştırabilmesi oldukça istenen bir özelliğe sahiptir.

Yukarıdaki bölümde zararla ilgili örtülü bir ifade var: Tarayıcılar aynı anda yalnızca bir işlem yapabilir. Bu kesin olarak doğru olmasa da, geçerli bir varsayımdır. Tarayıcı herhangi bir zamanda JS çalıştırabiliyor, düzen ya da boyama yapıyor olabilir, ama aynı anda yalnızca bir tanesini çalıştırıyor olabilir. Bu, Geliştirici Araçları'nın Zaman Çizelgesi görünümünde doğrulanabilir. Bu kuralın istisnalarından biri, Android için Chrome'da (ve yakında masaüstü Chrome'da da henüz mümkün olmasa da) CSS animasyonlarıdır.

Mümkün olduğunda CSS animasyonu kullanmak hem uygulamanızı basitleştirir hem de JavaScript çalışırken bile animasyonların sorunsuz çalışmasını sağlar.

  // see http://paulirish.com/2011/requestanimationframe-for-smart-animating/ for info on rAF polyfills
  rAF = window.requestAnimationFrame;

  var degrees = 0;
  function update(timestamp) {
    document.querySelector('#foo').style.webkitTransform = "rotate(" + degrees + "deg)";
    console.log('updated to degrees ' + degrees);
    degrees = degrees + 1;
    rAF(update);
  }
  rAF(update);

Düğmeyi tıklarsanız JavaScript 180 ms çalışır ve jant sorununa neden olur. Ancak bunun yerine animasyonu CSS animasyonlarıyla çalıştırırsak sorun artık gerçekleşmez.

(Bu yazının yazıldığı sırada CSS animasyonunun masaüstü Chrome'da değil, yalnızca Android için Chrome'da sorunsuz olduğunu unutmayın.)

  /* tools like Modernizr (http://modernizr.com/) can help with CSS polyfills */
  #foo {
    +animation-duration: 3s;
    +animation-timing-function: linear;
    +animation-animation-iteration-count: infinite;
    +animation-animation-name: rotate;
  }

  @+keyframes: rotate; {
    from {
      +transform: rotate(0deg);
    }
    to {
      +transform: rotate(360deg);
    }
  }

CSS Animasyonlarını kullanma hakkında daha fazla bilgi için MDN'deki bu makale gibi makalelere bakın.

Özet

Kısacası şu şekildedir:

  1. Animasyon yaparken her ekran yenilemesi için kare oluşturmak önemlidir. Vsync animasyon, uygulamanın algısı üzerinde son derece olumlu bir etki yaratır.
  2. Chrome'da ve diğer modern tarayıcılarda vsync'd animasyon almanın en iyi yolu kullanabilirsiniz. CSS animasyonundan daha fazla esnekliğe ihtiyacınız olduğunda en iyi teknik requestAnimationFrame tabanlı animasyondur.
  3. rAF animasyonlarının sağlıklı ve mutlu kalmasını sağlamak için diğer etkinlik işleyicilerin rAF geri çağırma işlevinize engel teşkil etmediğinden, rAF geri çağırmalarını kısa (<15 ms).

Son olarak, vsync animasyonu yalnızca basit kullanıcı arayüzü animasyonları için değil; Canvas2D animasyonu, WebGL animasyonu ve hatta statik sayfalarda kaydırma için de geçerlidir. Bu serinin bir sonraki makalesinde, bu kavramları göz önünde bulundurarak kaydırma performansını inceleyeceğiz.

İyi animasyonlar!

Referanslar