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. Çocuklara ters ölçek uygulayarak animasyon sırasında uzamalarını ve eğilmelerini önleyebilirsiniz.

Daha önce, yüksek performanslı paralel kaydırma efektleri ve sonsuz kaydırma oluşturma hakkında güncellemeler yayınlamıştık. Bu yayında, yüksek 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ü: Bir kapsayıcı öğesinde genişlik ve yüksekliği animasyonlu hale getirme

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 özellikler, 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 simgesini de kullanabilirsiniz. Ancak clip-path, clip'e kıyasla daha az desteklenir. 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 yolu tercih ederseniz clip mülkünün, üzerinde çalıştığı öğenin mutlak veya sabit konumda olmasını gerektirmesi, biraz daha fazla uğraş gerektirebilir.

İyi: ölçekleri animasyonlu hale getirme

Bu efektte bir öğenin büyüyüp küçülmesi söz konusu olduğundan ö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 sayede efekt hızlandırılır ve 60 fps'ye ulaşma olasılığı önemli ölçüde artar.

Oluşturma performansındaki çoğu şey gibi bu yaklaşımın da dezavantajı, biraz kurulum gerektirmesidir. Ancak 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), stiller son çalıştırıldığından beri 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ğı varsayılabilir. 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 ters 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 vardır:

  1. Karşı dönüşüm de bir ölçekleme işlemidir. Bu, kapsayıcıdaki animasyon gibi hızlandırılabileceği için iyi bir özelliktir. Canlandırılan öğelerin kendi kompozisyon katmanına sahip olmasını (GPU'nun yardımcı olmasını sağlamak için) sağlamanız gerekebilir. Bunun için öğeye will-change: transform veya eski tarayıcıları desteklemeniz gerekiyorsa backface-visiblity: hidden ekleyebilirsiniz.

  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şleminin animasyonunda yumuşatma işlevinin kendisinin de karşılanması gerekir. Ancak, örneğin 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 tuhaf 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ürler.) Bunun birincil avantajı, dönüşümleri değiştiren bir anahtar kare animasyonunun, birleştiricide çalıştırılabilmesidir. Yani ana iş parçacığındaki görevlerden etkilenmez.

Animasyon karelerini oluşturmak için 0 ile 100 arasında ilerler ve öğe ile içeriği için hangi ölçek değerlerinin gerekli olacağı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'e 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 işlem, önceki adımda oluşturulan animasyonların çalışmasına neden olur. Pişirilmiş animasyonlar zaten yumuşatılmış olduğundan zamanlama işlevinin linear olarak ayarlanması gerekir. Aksi takdirde, her bir ana kare arasında yumuşatma uygulanır ve bu da çok garip 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. Daha uygun bir çözüm, öğeyi daraltmak için ikinci bir animasyon çifti oluşturmaktı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ı: Chrome, metnin ölçeği ve karşı ölçeği nedeniyle yuvarlama hatalarından dolayı animasyon sırasında düşük DPI ekranlarda bulanık metinlere sahiptir. Bu konuyla ilgili ayrıntıları öğrenmek istiyorsanız öne çıkanlar listenize ekleyip takip edebileceğiniz bir hata kaydı mevcuttur.

Dairesel genişleme efektinin kodunu GitHub deposunda bulabilirsiniz.

Sonuçlar

İşte ölçek dönüşümlerini kullanarak performanslı klip animasyonları oluşturmanın bir yolu. İ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 eklerken dikkatli olmalı ve width veya height'e animasyon eklemekten kesinlikle kaçınmalısınız.

Bu tür efektler için Web Animasyonları'nı kullanmak da faydalı olabilir. Çünkü bu animasyonlar JavaScript API'sine sahiptir ancak yalnızca transform ve opacity'i animasyona tabi tutarsanız karıştırıcı iş parçacığında ç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 durum değişene kadar, animasyon yapmak için JavaScript tabanlı kitaplıkları kullanabilir olsanız da bir CSS animasyonu oluşturup bunun yerine kullanarak daha güvenilir performans elde edebilirsiniz. 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 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.