Bina performansı genişletme ve daraltma animasyonları

Paul Lewis
Stephen McGruer
Stephen McGruer

Özet

Klipleri animasyona dönüştürürken ölçek dönüştürmelerini kullanın. Alt öğeleri karşı ölçeklendirerek animasyon sırasında esnemelerini ve bükmelerini önleyebilirsiniz.

Daha önce, yüksek performanslı paralaks efektleri ve sonsuz kaydırıcılar oluşturmaya ilişkin güncellemeler yayınlamıştık. Bu yayında, performanslı klip animasyonlarına sahip olmak için neler yapmanız gerektiğine göz atacağız. Demo görmek isterseniz Örnek Kullanıcı Arayüzü Öğeleri GitHub deposuna göz atın.

Örneğin, genişleyen bir menüyü ele alalım:

Bu tür bir sayfa oluşturmak için kullanabileceğiniz seçeneklerden bazıları diğerlerinden daha iyi performans gösterir.

Kötü: Kapsayıcı öğesinde genişlik ve yükseklik canlandırılıyor

Kapsayıcı öğesindeki genişlik ve yüksekliği animasyonlu hale getirmek için biraz CSS kullanabilirsiniz.

.menu {
  overflow: hidden;
  width: 350px;
  height: 600px;
  transition: width 600ms ease-out, height 600ms ease-out;
}

.menu--collapsed {
  width: 200px;
  height: 60px;
}

Bu yaklaşımın hemen fark edilen sorunu, width ve height öğelerinin animasyonlu olması gerektiğidir. Bu mülkler, düzenin hesaplanmasını ve sonuçların animasyonun her karesine boyanmasını gerektirir. Bu işlem çok pahalı olabilir ve genellikle 60 fps'yi kaçırmanıza neden olur. Bu konu hakkında bilginiz yoksa oluşturma sürecinin işleyiş şekli hakkında daha fazla bilgi edinebileceğiniz Oluşturma Performansı kılavuzlarımızı okuyun.

Kötü: CSS clip veya clip-path özelliklerini kullanın

width ve height'e animasyon uygulamanın alternatifi, genişlet ve daralt efektini animasyonlu hale getirmek için (artık desteği sonlandırılmış) clip özelliğini kullanmak olabilir. Dilerseniz bunun yerine clip-path öğesini de kullanabilirsiniz. Ancak clip-path kullanımı, clip ürününe kıyasla daha az iyi desteklenmiyor. Ancak clip desteği sonlandırıldı. Sağ. Ancak endişelenmeyin, bu zaten istediğiniz çözüm değil.

.menu {
  position: absolute;
  clip: rect(0px 112px 175px 0px);
  transition: clip 600ms ease-out;
}

.menu--collapsed {
  clip: rect(0px 70px 34px 0px);
}

Bu yaklaşım, menü öğesinin width ve height özelliklerini animasyonla değiştirmekten daha iyi olsa da boya işlemini tetiklemeye devam etmesinin dezavantajı vardır. Ayrıca, bu rotaya giderseniz clip özelliği, üzerinde çalıştığı öğenin kesinlikle veya sabit konumda olmasını gerektirir. Bu da biraz daha fazla zorlanmasını gerektirebilir.

İyi: Ölçekleri canlandırma

Bu etki bir şeyin büyüyüp küçülmesini gerektirdiğinden ölçek dönüşümü kullanabilirsiniz. Dönüşümleri değiştirmek düzenleme veya boyama gerektirmediği ve tarayıcının GPU'ya aktarabildiği bir işlem olduğundan bu, harika bir haber. Bu da efektin hızlandığı ve 60 fps'ye ulaşma olasılığının önemli ölçüde arttığı anlamına geliyor.

Oluşturma performansındaki çoğu şey gibi bu yaklaşımın da dezavantajı, biraz kurulum gerektirmesidir. Yine de buna değer.

1. adım: Başlangıç ve bitiş durumlarını hesaplayın

Ölçek animasyonlarını kullanan bir yaklaşımda ilk adım, menünün hem daraltıldığında hem de genişletildiğinde olması gereken boyutu belirten öğeleri okumaktır. Bazı durumlarda bu iki bilgi parçasını tek seferde alamayabilirsiniz ve bileşenin çeşitli durumlarını okuyabilmek için bazı sınıfları değiştirmeniz gerekebilir. Ancak bunu yapmanız gerekiyorsa dikkatli olun: getBoundingClientRect() (veya offsetWidth ve offsetHeight), son çalıştırıldığından beri stiller değiştiyse tarayıcıyı stilleri ve düzen geçişlerini çalıştırmaya zorlar.

function calculateCollapsedScale () {
    // The menu title can act as the marker for the collapsed state.
    const collapsed = menuTitle.getBoundingClientRect();

    // Whereas the menu as a whole (title plus items) can act as
    // a proxy for the expanded state.
    const expanded = menu.getBoundingClientRect();
    return {
    x: collapsed.width / expanded.width,
    y: collapsed.height / expanded.height
    };
}

Menü gibi bir öğe söz konusu olduğunda, doğal ölçeğinde (1, 1) başlayacağını varsayabiliriz. Bu doğal ölçek, öğenin genişletilmiş halini temsil eder. Yani, küçültülmüş bir sürümden (yukarıda hesaplandı) bu doğal ölçeğe kadar animasyon oluşturmanız gerekir.

Ama, bir saniye! Bu, menünün içeriğini de ölçeklendirir, değil mi? Evet, aşağıda görebilirsiniz.

Peki bu konuda ne yapabilirsiniz? İçeriğe karşı dönüşüm uygulayabilirsiniz. Örneğin, kapsayıcı normal boyutunun 1/5'ine ölçeklendirilirse içeriğin sıkıştırılmasını önlemek için içeriği 5 kat büyütebilirsiniz. Bu konuda dikkat edilmesi gereken iki nokta var:

  1. Karşı dönüşüm de bir ölçekleme işlemidir. Bu, kapsayıcıdaki animasyon gibi hızlandırılabileceğinden iyidir. Animasyonlu öğelerin kendi birleştirici katmanına sahip olmasını (GPU'nun yardımcı olmasını) sağlamanız ve bunun için öğeye will-change: transform veya eski tarayıcıları desteklemeniz gerekiyorsa backface-visiblity: hidden öğesini eklemeniz gerekebilir.

  2. Karşı dönüşüm kare başına hesaplanmalıdır. Bu noktada işler biraz daha karmaşık hale gelebilir. Çünkü animasyonun CSS'de olduğu ve bir yumuşatma işlevi kullandığı varsayıldığında, karşı dönüştürme işlemini animasyona dönüştürürken yumuşatma işlevinin kendisinin de karşılanması gerekir. Ancak cubic-bezier(0, 0, 0.3, 1) için ters eğriyi hesaplamak o kadar da kolay değildir.

Bu nedenle, efekti JavaScript kullanarak animasyonlu hale getirmeyi düşünmek cazip gelebilir. Sonuçta, kare başına ölçek ve karşı ölçek değerlerini hesaplamak için bir yumuşatma denklemi kullanabilirsiniz. JavaScript tabanlı animasyonların dezavantajı, ana iş parçacığı (JavaScript'inizin çalıştığı yer) başka bir görevle meşgul olduğunda ortaya çıkar. Kısa yanıt, animasyonunuzun takılabilir veya tamamen durabilir olmasıdır. Bu durum kullanıcı deneyimi açısından iyi değildir.

2. Adım: CSS animasyonlarını anında oluşturun

İlk başta garip görünebilecek çözüm, kendi kolaylaştırma işlevimizle dinamik olarak bir anahtar kare animasyonu oluşturmak ve menünün kullanması için sayfaya yerleştirmektir. (Bunu fark ettiği için Chrome mühendisi Robert Flack'a çok teşekkür ederiz.) Bunun birincil avantajı, dönüştürme işlemlerini değiştiren bir anahtar kare animasyonunun, birleştiricide çalıştırılabilmesidir. Yani ana iş parçacığındaki görevlerden etkilenmez.

Animasyon karesi animasyonunu oluşturmak için 0 ile 100 arasında bir adım atarak öğe ve içeriği için hangi ölçek değerlerine ihtiyaç duyulacağını hesaplarız. Bunlar daha sonra bir dize haline getirilebilir ve sayfaya stil öğesi olarak eklenebilir. Stillerin eklenmesi, sayfada Stilleri Yeniden Hesapla geçişine neden olur. Bu, tarayıcı tarafından yapılması gereken ek bir iştir ancak bileşen başlatılırken yalnızca bir kez yapılır.

function createKeyframeAnimation () {
    // Figure out the size of the element when collapsed.
    let {x, y} = calculateCollapsedScale();
    let animation = '';
    let inverseAnimation = '';

    for (let step = 0; step <= 100; step++) {
    // Remap the step value to an eased one.
    let easedStep = ease(step / 100);

    // Calculate the scale of the element.
    const xScale = x + (1 - x) * easedStep;
    const yScale = y + (1 - y) * easedStep;

    animation += `${step}% {
        transform: scale(${xScale}, ${yScale});
    }`;

    // And now the inverse for the contents.
    const invXScale = 1 / xScale;
    const invYScale = 1 / yScale;
    inverseAnimation += `${step}% {
        transform: scale(${invXScale}, ${invYScale});
    }`;

    }

    return `
    @keyframes menuAnimation {
    ${animation}
    }

    @keyframes menuContentsAnimation {
    ${inverseAnimation}
    }`;
}

Meraklılar, for döngüsü içindeki ease() işlevini merak edebilir. 0 ile 1 arasındaki değerleri kolayca anlaşılır bir eşdeğerle eşlemek için buna benzer bir yöntem kullanabilirsiniz.

function ease (v, pow=4) {
  return 1 - Math.pow(1 - v, pow);
}

Google Arama'yı kullanarak da bu konumun nasıl göründüğünü görebilirsiniz. Çok kullanışlı. Başka yumuşatma denklemlerine ihtiyacınız varsa Soledad Penadés tarafından oluşturulan Tween.js'ye göz atın. Bu kitaplıkta çok sayıda yumuşatma denklemi bulunur.

3. Adım: CSS Animasyonlarını etkinleştirin

Bu animasyonlar oluşturulup JavaScript'de sayfaya yerleştirildikten sonra son adım, sınıfları değiştirerek animasyonları etkinleştirmektir.

.menu--expanded {
  animation-name: menuAnimation;
  animation-duration: 0.2s;
  animation-timing-function: linear;
}

.menu__contents--expanded {
  animation-name: menuContentsAnimation;
  animation-duration: 0.2s;
  animation-timing-function: linear;
}

Bu, önceki adımda oluşturulan animasyonların çalıştırılmasına neden olur. Fırında pişmiş animasyonlar zaten yumuşatılmış olduğundan zamanlama işlevinin linear değerine ayarlanması gerekir. Aksi takdirde, her animasyon karesi arasında kolaylık sağlarsınız ve her şey çok tuhaf görünür.

Öğeyi tekrar daraltmak için iki seçenek vardır: CSS animasyonunu ileri yerine geriye doğru çalışacak şekilde güncelleyin. Bu işlem sorunsuz bir şekilde çalışır ancak animasyonun "hissi" tersine çevrilir. Bu nedenle, yavaşlama eğrisi kullandıysanız ters işlem yavaş başlayacaktır ve bu da animasyonun yavaş görünmesine neden olur. Öğeyi daraltmak için ikinci bir animasyon çifti oluşturmak daha uygun bir çözümdür. Bunlar, genişlet anahtar kare animasyonlarıyla tam olarak aynı şekilde oluşturulabilir ancak başlangıç ve bitiş değerleri değiştirilir.

const xScale = 1 + (x - 1) * easedStep;
const yScale = 1 + (y - 1) * easedStep;

Daha gelişmiş bir sürüm: dairesel açıklama metinleri

Bu tekniği, dairesel genişleme ve daralma animasyonları oluşturmak için de kullanabilirsiniz.

İlkeler, bir öğeyi ölçeklendirip doğrudan alt öğelerini ters ölçeklendirdiğiniz önceki sürümle büyük ölçüde aynıdır. Bu örnekte, ölçeği büyütülen öğenin border-radius değeri %50'dir ve bu nedenle öğe daireseldir. Ayrıca, overflow: hidden değerine sahip başka bir öğe tarafından sarmalanmıştır. Bu sayede, dairenin öğe sınırlarının dışına genişlediğini görmezsiniz.

Bu varyantla ilgili bir uyarı: Metnin ölçeği ve karşı ölçeği nedeniyle yuvarlama hataları nedeniyle, animasyon sırasında Chrome'un düşük DPI ekranlarında bulanık metin gösteriliyor. Bu konuyla ilgili ayrıntıları öğrenmek istiyorsanız beğenip takip edebileceğiniz bir hata kaydı mevcuttur.

Dairesel genişleme efektinin kodunu GitHub deposunda bulabilirsiniz.

Sonuçlar

Ölçek dönüşümlerini kullanarak yüksek performanslı klip animasyonları yapmanın bir yolunu burada bulabilirsiniz. İdeal bir dünyada, klip animasyonlarının hızlandırılmasını görmek harika olurdu (Jake Archibald tarafından oluşturulan bu Chromium hatası mevcuttur). Ancak bu duruma ulaşana kadar clip veya clip-path'e animasyon uygularken dikkatli olmalı ve width veya height'e animasyon uygulamaktan kesinlikle kaçınmalısınız.

Ayrıca, bunun gibi efektler için Web Animasyonları'nı kullanmak da yararlı olacaktır. Çünkü bunlar JavaScript API'sine sahiptir, ancak yalnızca transform ve opacity canlandırdığınızda birleştirici iş parçacığı üzerinde çalışabilir. Maalesef web animasyonları için destek çok iyi değil. Ancak mevcutsa bunları kullanmak için aşamalı geliştirmeyi kullanabilirsiniz.

if ('animate' in HTMLElement.prototype) {
    // Animate with Web Animations.
} else {
    // Fall back to generated CSS Animations or JS.
}

Bu değişiklik yapılana kadar, animasyonu yapmak için JavaScript tabanlı kitaplıkları kullanabilirsiniz ancak bunun yerine bir CSS animasyonu hazırlayıp bunu kullanarak daha güvenilir bir performans elde edebileceğinizi görebilirsiniz. Benzer şekilde, uygulamanız animasyonlar için zaten JavaScript kullanıyorsa en azından mevcut kod tabanınızla tutarlı bir şekilde hareket etmeniz daha iyi sonuçlar almanıza yardımcı olabilir.

Bu efektin kodunu incelemek isterseniz UI Element Samples GitHub deposuna göz atın. Her zaman olduğu gibi, bu efektle ilgili deneyimlerinizi aşağıdaki yorumlar bölümünde bizimle paylaşın.