Bina performansı genişletme ve daraltma animasyonları

Paul Lewis
Stephen McGruer'ın yer aldığı daha fazla içerik
Stephen McGruer

Özet

Klipleri canlandırırken ölçek dönüştürmelerini kullanın. Çocukların animasyon sırasında esnemelerini ve eğmelerini engellemek için karşı ölçeklendirme yapabilirsiniz.

Daha önce, yüksek performanslı paralaks efektleri ve sonsuz kaydırıcılar oluşturma ile ilgili güncellemeler yayınlamıştık. Bu gönderide, yüksek performanslı klip animasyonları istemeniz durumunda nelerin gerekli olduğuna bakacağız. Bir demo görmek istiyorsanız Sample UI Elements GitHub deposuna göz atın.

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

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

Kötü: Kapsayıcı öğesinin genişliğine ve yüksekliğine animasyon ekleme

Kapsayıcı öğenin genişliğini ve yüksekliğini 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şımda en öncelikli sorun, width ve height animasyonlarının gerekli olmasıdır. Bu özellikler düzenin hesaplanmasını gerektirir ve sonuçları animasyonun her karesinde boyar. Bu da çok pahalı olabilir ve genellikle 60 fps'yi kaçırmanıza neden olur. Bu konuda bir haberiniz varsa oluşturma işleminin nasıl çalıştığı hakkında daha fazla bilgi edinebileceğiniz Oluşturma Performansı kılavuzlarımızı okuyun.

Kötü: CSSClip veyaClip-path özelliklerini kullanın.

width ve height animasyonlarına alternatif olarak, genişletme ve daraltma efektini canlandırmak için (şu anda kullanımdan kaldırılmış olan) clip özelliğini kullanabilirsiniz. İsterseniz bunun yerine clip-path politikasını da kullanabilirsiniz. Bununla birlikte, clip-path kullanımı clip olduğundan daha az desteklenir. Ancak clip desteği sonlandırıldı. Sağ. Ancak üzülmeyin, zaten 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 öğelerini canlandırmaktan daha iyi olsa da bu yaklaşımın olumsuz tarafı, boyamayı tetiklemeye devam etmesidir. Ayrıca, bu rotayı izlerseniz clip özelliği, üzerinde çalıştığı öğenin kesinlikle veya sabit konumda olmasını gerektirir. Bu da biraz daha fazla hazırlama gerektirebilir.

İyi: Ölçekleri canlandırma

Bu efekt, bir şeyin büyüyüp küçülmesini gerektirdiği için bir ölçek dönüşümü kullanabilirsiniz. Bu harika bir haber. Çünkü dönüşümleri değiştirmek düzen veya boya gerektirmeyen ve tarayıcı GPU'ya aktarılabilecek bir şey olduğundan etki hızlanır ve 60 fps'ye çıkma olasılığı önemli ölçüde daha yüksektir.

Bu yaklaşımın dezavantajı, oluşturma performansındaki çoğu şey gibi biraz kurulum gerektirmesidir. Ama buna değecek!

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 hangi boyutta olması gerektiğini belirten öğeleri okumaktır. Bazı durumlarda, bu iki bilgiyi de tek seferde alamamanız ve 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: getBoundingClientRect() (veya offsetWidth ve offsetHeight), son çalıştırma sonrasında 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 durumda, menünün doğal ölçeğinde (1, 1) yer alacağına dair makul bir varsayımda bulunabiliriz. Bu doğal ölçek, genişletilmiş durumunu temsil eder. Diğer bir deyişle, yukarıda hesaplanmış olan, ölçeği küçültülmüş bir sürümden bu doğal ölçeğe geri dönmeniz gerekir.

Ama, bir saniye! Bu elbette menünün içeriğini de ölçeklendirecektir, değil mi? Aşağıda görebileceğiniz gibi, evet.

Peki, bu konuda ne yapabilirsiniz? İçeriğe bir counter- uygulayabilirsiniz. Dolayısıyla, örneğin kapsayıcı normal boyutunun 1/5'ine küçültülürse, içeriğin sıkıştırılmasını önlemek için içeriği 5 kat counter- ölçeklendirebilirsiniz. Bu konuda dikkat edilmesi gereken iki nokta vardır:

  1. Karşı dönüşüm aynı zamanda bir ölçeklendirme işlemidir. Kapsayıcıdaki animasyonda olduğu gibi hızlandırılabileceği için bu iyi. Animasyonlu öğelerin kendi birleştirici katmanlarını almasını (GPU'nun yardımcı olmasını sağlar) ve 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. Burada işler biraz karışabilir. Çü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şatmanın kendisinin karşılanması gerekir. Ancak, örneğin cubic-bezier(0, 0, 0.3, 1) için ters eğriyi hesaplamak o kadar bariz değildir.

Bu nedenle, efektin JavaScript'i kullanarak animasyonunu kullanmayı 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 olumsuz tarafı, ana iş parçacığı (JavaScript'inizin çalıştığı) başka bir görevle meşgul olduğunda ortaya çıkar. Bunun kısa yanıtı, animasyonunuzun takılması veya tamamen durabilmesidir. Bu, kullanıcı deneyimi için iyi değildir.

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

Başlangıçta tuhaf görünebilecek olan çözüm, kendi yumuşak geçiş işlevimizle dinamik bir şekilde animasyon karesi içeren bir animasyon oluşturmak ve bunu menünün kullanması için sayfaya eklemektir. (Bu konuya değindiği için Chrome mühendisi Robert Flack'e çok teşekkür ederiz!) Bunun birincil avantajı, dönüşümleri değiştiren animasyon kareli bir animasyonun birleştirici üzerinde çalıştırılabilmesi, yani ana iş parçacığındaki görevlerden etkilenmemesidir.

Animasyon karesi animasyonunu yapmak için 0'dan 100'e bir adım atarız ve bu öğe ile içeriği için hangi ölçek değerlerinin gerekli olacağını hesaplarız. Bunlar daha sonra bir dizeye dönüştürülebilir ve sayfaya stil öğesi olarak yerleştirilebilir. Stillerin eklenmesi sayfada Stilleri Yeniden Hesapla geçişine neden olur. Bu, tarayıcının yapması gereken ek işlemdir, ancak bu işlem yalnızca bileşen başlatılırken 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}
    }`;
}

Sonsuza kadar merak duyanlar ise for döngüsü içindeki ease() işlevini merak ediyor olabilir. 0 ile 1 arasındaki değerleri kolaylaştırılmış bir eşdeğerle eşlemek için böyle bir işlev kullanabilirsiniz.

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

Neye benzediğini göstermek için Google aramayı da kullanabilirsiniz. Çok kullanışlı! Başka yumuşatma denklemlerine ihtiyacınız varsa bunların tamamını içeren Tween.js by Soledad Penadés'e 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ştirecek sınıfları açmaktı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ışmasına neden olur. Oluşturulan animasyonlar zaten yumuşatılmış olduğundan zamanlama işlevinin linear olarak ayarlanması gerekir. Aksi takdirde, her animasyon karesi arasında geçiş yapmanız çok garip görünür.

Öğenin tekrar daraltılması söz konusu olduğunda iki seçenek vardır: CSS animasyonunu ileri doğru değil, tersine çalışacak şekilde güncelleyin. Bu işlem sorunsuz olur ancak animasyonun "hissi" tersine çevrilecektir. Böylece bir yumuşak çıkış eğrisi kullandıysanız tersi içe yavaşlamış hissedeceğiniz için yavaşlamış hissedilir. Öğ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 açılış

Bu tekniği dairesel genişletme ve daraltma animasyonları yapmak için de kullanmak mümkündür.

İlkeler, bir öğeyi ölçeklendirdiğiniz ve bu öğenin ilk alt öğelerini karşı ölçeklendirdiğiniz önceki sürümle büyük ölçüde aynıdır. Bu durumda, ölçeği artan öğenin border-radius değeri %50'dir. Bu değer dairesel hale gelir ve overflow: hidden içeren başka bir öğeyle sarmalanır. Diğer bir deyişle, dairenin öğe sınırlarının dışına genişlediğini görmezsiniz.

Bu varyantla ilgili uyarı mesajı: Metnin ölçeği ve karşı ölçeği nedeniyle yuvarlama hataları nedeniyle Chrome'un animasyon sırasında düşük DPI ekranlarda bulanık metin var. Bununla ilgili ayrıntılarla ilgileniyorsanız bir hata kaydı oluşturabilirsiniz. Yıldız ekledik ve takip edebilirsiniz.

Döngüsel genişletme efektinin kodu GitHub deposunda bulunabilir.

Sonuçlar

İşte bu kadar. Ölçeklendirme dönüşümlerini kullanarak yüksek performanslı klip animasyonları yapmanın bir yolu var. Kusursuz bir dünyada, klip animasyonlarının hızlandırıldığını görmek harika olurdu (Bunun için Jake Archibald tarafından yapılmış bir Chromium hatası var), ancak biz bu noktaya ulaşana kadar clip veya clip-path animasyonlarını yaparken dikkatli olmalı ve width veya height animasyonlarından kesinlikle kaçınmalısınız.

Ayrıca, bunun gibi efektler için Web Animasyonları'nı kullanmak da yararlı olur. Bunun nedeni, bu efektlerin JavaScript API'ye sahip olması ancak yalnızca transform ve opacity animasyonlarını canlandırırsanız birleştirici iş parçacığında çalışabilmeleridir. Maalesef Web Animasyonları için destek o kadar iyi değildir, ancak varsa bunları kullanmak için progresif geliştirme kullanabilirsiniz.

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

Bu değişiklik olana kadar, animasyonu yapmak için JavaScript tabanlı kitaplıkları kullanabilirsiniz. Ancak bir CSS animasyonu oluşturup bunun yerine bunu kullanarak daha güvenilir performans elde edebileceğinizi görebilirsiniz. Aynı şekilde, uygulamanızın animasyonları için JavaScript'i zaten kullanıyorsa en azından mevcut kod tabanınızla tutarlı olmanız, size daha iyi hizmet sunabilir.

Bu efekti almak için koda göz atmak isterseniz UI Element Samples GitHub deposuna göz atın ve her zaman olduğu gibi aşağıdaki yorumlardan nasıl ulaştığınızı bize bildirin.