Bina performansı genişletme ve daraltma animasyonları

Paul Lewis
Stephen McGruer
Stephen McGruer

Özet

Klipleri canlandı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. Yüksek performans gösteren klip animasyonları istiyorsanız bu gönderide nelere değineceğiz. Demo görmek istiyorsanız Sample UI Elements GitHub deposuna göz atın.

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

Bunu oluşturmaya yönelik bazı seçenekler diğerlerinden daha iyi performans gösterir.

Kötü: Bir kapsayıcı öğesinde genişlik ve yüksekliğin animasyonlanması

Kapsayıcı öğede genişlik ve yüksekliği canlandırmak için biraz CSS kullanmayı düşünebilirsiniz.

.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 şu andaki sorunu, width ve height animasyonlarının uygulanmasını gerektirmesidir. Bu özellikler düzeni hesaplamayı gerektirir ve sonuçları animasyonun her karesi için boyayabilir. Bu işlem çok pahalı olabilir ve genellikle 60 fps'yi kaçırmanıza neden olur. Bu sizin için yeniyse oluşturma sürecinin nasıl çalıştığı hakkında daha fazla bilgi edinebileceğiniz Oluşturma Performansı kılavuzlarını okuyun.

Kötü: CSS klip veya klip yolu özelliklerini kullanın.

width ve height animasyonlarına alternatif olarak, genişletme ve daraltma efektine animasyon eklemek için (artık kullanımdan kaldırıldı) clip özelliğini kullanabilirsiniz. Dilerseniz bunun yerine clip-path öğesini de kullanabilirsiniz. Ancak clip-path kullanımı, clip ürününe kıyasla daha az iyi şekilde desteklenmiyor. Ancak clip desteği sonlandırıldı. Sağ. Ama üzülmeyin, istediğiniz çözüm bu değildi.

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

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

Menü öğesinin width ve height animasyonlarını canlandırmaktan daha iyi olsa da bu yaklaşımın olumsuz yanı, boyamayı tetiklemeye devam etmesidir. 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. Bu harika bir haber çünkü dönüşümleri değiştirmek düzen veya boyama gerektirmediğinden tarayıcı GPU'ya devredebilir. Bu da etkinin hızlandırıldığı ve 60 fps'ye ulaşma ihtimalinin önemli ölçüde daha yüksek olduğu anlamına gelir.

Bu yaklaşımın dezavantajı, oluşturma performansındaki çoğu şey gibi, biraz ayarlama gerektirmesidir. Yine de buna değer.

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

Ölçek animasyonlarının kullanıldığı bir yaklaşımda ilk adım, menünün hem daraltıldığında hem de genişletildiğinde ne kadar olması gerektiğini belirten öğeleri okumaktır. Bazı durumlarda bu bilgilerin her ikisini de tek seferde alamayabilirsiniz. Bileşenin çeşitli durumlarını okuyabilmek için bazı sınıflar arasında geçiş yapmanız gerekebilir. Ancak bunu yapmanız gerekirse dikkatli olun: Son çalıştırmadan bu yana stiller değiştiyse getBoundingClientRect() (veya offsetWidth ve offsetHeight), 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 şey söz konusu olduğunda, menünün kendi doğal ölçeğinde (1, 1) oluşmaya başlayacağına dair makul varsayımda bulunabiliriz (1, 1). Bu doğal ölçek, onun genişletilmiş durumunu temsil eder. Yani, yukarıda hesaplanmış olan küçültülmüş sürümden bu doğal ölçeğe kadar animasyon oluşturmanız gerekir.

Ama, bir saniye! Elbette bu, menünün içeriğini de ölçeklendirir, değil mi? Aşağıda görebileceğiniz gördüğünüz gibi, evet.

Peki, bu konuda ne yapabilirsiniz? İçeriğe bir counter-dönüşümü uygulayabilirsiniz. Örneğin, container normal boyutunun 1/5'ine kadar ölçeklendirilirse içeriğin sıkıştırılmasını önlemek için içeriği 5 kat yukarı ölçeklendirebilirsiniz. Bu konuda dikkat edilmesi gereken iki nokta var:

  1. Sayaç dönüşüm aynı zamanda bir ölçeklendirme 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. Sayaç dönüşüm, kare başına hesaplanmalıdır. Burada işler biraz daha karmaşık hale gelebilir. Çünkü animasyonun CSS'de olduğu ve bir yumuşak geçiş işlevi kullandığı varsayıldığında, karşı dönüşümü canlandırırken yumuşak geçişin kendisinden geçmesi gerekir. Ancak cubic-bezier(0, 0, 0.3, 1) için ters eğriyi hesaplamak her zaman doğru bir sonuç değildir.

Bu durumda, JavaScript'i kullanarak etkiyi canlandırmak cazip gelebilir. Sonuçta, kare başına ölçeği ve karşı ölçek değerlerini hesaplamak için bir yumuşatma denklemi kullanabilirsiniz. JavaScript tabanlı animasyonların olumsuz tarafı, ana iş parçacığı (JavaScript'inizin çalıştığı) başka bir görevle meşgul olduğunda olanlardır. Kısa yanıtı, animasyonunuzun takılması veya tamamen durabilmesidir. Bu, kullanıcı deneyimi için iyi değildir.

2. Adım: Anında CSS Animasyonlarını oluşturun

İlk başta tuhaf görünebilecek çözüm, kendi yumuşatma işlevimizle dinamik olarak animasyon karesi içeren bir animasyon oluşturmak ve bunu menü tarafından kullanılmak üzere sayfaya eklemektir. (Bunu belirttiği için Chrome mühendisi Robert Flack'e çok teşekkür ederim.) Bunun birincil avantajı, dönüşümleri değiştiren animasyon kareli bir animasyonun birleştirici üzerinde çalıştırılabilmesidir. Diğer bir deyişle, 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 dizeye dönüştürülebilir. Bu dizeler, sayfaya bir stil öğesi olarak eklenebilir. Stillerin yerleştirilmesi, sayfada Stilleri Yeniden Hesapla geçişi neden olur. Bu, tarayıcının yapması gereken ek bir çalışmadır, ancak bunu yalnızca bileşen başlatılırken yapar.

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}
    }`;
}

Sürekli merak eden kişiler, for döngüsündeki ease() işlevini merak ediyor olabilir. 0'dan 1'e kadar olan değerleri yumuşatılmış bir eşdeğeriyle eşlemek için bunun gibi bir şey kullanabilirsiniz.

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

Google Arama'yı kullanarak neyin nasıl görüneceğini belirleyebilirsiniz. Kullanışlı! Başka yumuşak geçiş denklemlerine ihtiyacınız varsa bunların tümünü içeren Tween.js by Soledad Penadés kitaplığına göz atın.

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

Bu animasyonlar oluşturulup JavaScript'te sayfaya yerleştirildikten sonra, son adım, animasyonları etkinleştiren sınıflar arasında geçiş yapmaktır.

.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. Pişmiş animasyonlar zaten yumuşatılmış olduğundan, zamanlama işlevinin linear değerine ayarlanması gerekir, aksi takdirde her animasyon karesi arasında kolayca geçiş yaparsınız ve her şey çok tuhaf görünür.

Öğeyi tekrar aşağı daraltmak söz konusu olduğunda iki seçenek vardır: CSS animasyonunu ileriye doğru değil, terste çalışacak şekilde güncelleyin. Bu işlem sorunsuz çalışır, ancak animasyonun "hissi" tersine çevrilir. Dolayısıyla, bir yumuşak geçiş eğrisi kullandıysanız tersi yumuşak içeri olur ve yavaşlamış gibi görünür. Öğeyi daraltmak için ikinci bir animasyon çifti oluşturmak daha uygun bir çözümdür. Bunlar, genişletme animasyon karesi animasyonlarıyla tam olarak aynı şekilde, ancak başlangıç ve bitiş değerleri değiştirilmiş olarak oluşturulabilir.

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

Daha gelişmiş bir sürüm: Dairesel ortaya çıkarır

Bu teknik, dairesel genişletme ve daraltma animasyonları yapmak için de kullanılabilir.

İlkeler, bir öğeyi ölçeklendirdiğiniz ve en sonraki alt öğelerini karşı ölçeklendirdiğiniz önceki sürümle büyük ölçüde aynıdır. Bu örnekte, ölçeği %50 olan border-radius öğesi dairesel hale gelir ve overflow: hidden içeren başka bir öğeyle sarılır. Yani, dairenin öğe sınırlarının dışına taştığını görmezsiniz.

Bu varyantla ilgili bir uyarı: Metnin ölçeğinden ve karşıt ölçeğinden kaynaklanan yuvarlama hataları nedeniyle, animasyon sırasında Chrome'un düşük DPI ekranlarında bulanık metin görünüyor. Bununla ilgili ayrıntılarla ilgileniyorsanız yıldız ekleyip takip edebileceğiniz bir hata kaydı mevcuttur.

Döngüsel genişletme efektinin koduna GitHub deposunda ulaşabilirsiniz.

Sonuçlar

Ölçek dönüşümlerini kullanarak yüksek performanslı klip animasyonları yapmanın bir yolunu burada bulabilirsiniz. Kusursuz bir dünyada, klip animasyonlarının hızlandırıldığını görmek harika olur (Jake Archibald tarafından bunun için bir Chromium hatası vardır) ancak biz bunu başarana kadar clip veya clip-path canlandırmaları yaparken dikkatli olmalı ve width ya da height animasyonu yapmaktan 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. Ne yazık ki Web Animasyonları için destek çok iyi değil, ancak varsa progresif geliştirmeyi kullanarak bunları 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. Aynı şekilde, uygulamanız animasyonları için zaten JavaScript'i kullanıyorsa en azından mevcut kod tabanınızla tutarlı olursanız daha iyi hizmet alabilirsiniz.

Bu efektin koduna göz atmak isterseniz UI Element Samples GitHub deposuna göz atın ve her zaman olduğu gibi, aşağıdaki yorumlar bölümünde neler olduğunu bize bildirin.