Houdini'nin Animasyon İş Akışı

Web uygulamanızın animasyonlarını güçlendirin

TL;DR: Animasyon İş Akışı, bu ekstra telaşsız smoothnessTM için cihazın kendi kare hızında çalışan zorunlu animasyonlar yazmanıza, animasyonlarınızı ana iş parçacığı duruşlarına karşı daha dayanıklı hale getirmenize ve zaman yerine kaydırmaya bağlanabilir hale getirmenize olanak tanır. Animasyon İş Akışı, Chrome Canary'de ("Deneysel Web Platformu özellikleri" işaretinin arkasında) bulunmaktadır ve Chrome 71 için bir Kaynak Denemesi planlıyoruz. Bu özelliği bugün aşamalı bir geliştirme olarak kullanmaya başlayabilirsiniz.

Başka bir Animasyon API'si mi var?

Aslında hayır, bu zaten sahip olduğumuzun bir uzantısı ve iyi bir nedeni var! En baştan başlayalım. Bugün web'de herhangi bir DOM öğesini canlandırmak istiyorsanız 2 1⁄2 seçeneğiniz vardır: basit A'dan B'ye geçişler için CSS Geçişleri, döngüsel ve daha karmaşık, zamana dayalı animasyonlar için CSS Animasyonları ve neredeyse rastgele karmaşık animasyonlar için Web Animasyonları API'si (WAAPI) WAAPI'nin destek matrisi oldukça acımasız görünüyor, ancak ilerliyor. O zamana kadar bir polyfill mevcut olur.

Tüm bu yöntemlerin ortak özelliği, durum bilgisiz ve zamana bağlı olmalarıdır. Ancak geliştiricilerin denediği etkilerden bazıları zaman odaklı veya durum bilgisiz değil. Örneğin, adından da anlaşılacağı üzere paralaks kaydırma aracı, kaydırmaya dayalı bir yapıya sahiptir. Günümüzde web'de yüksek performanslı bir paralaks kaydırma aracını uygulamak şaşırtıcı derecede zor.

Peki durumsizlik ne olacak? Örneğin, Android'deki Chrome'un adres çubuğunu düşünün. Ekranı aşağı kaydırırsanız görünüm dışında kalır. Ancak, sayfayı yukarı kaydırdığınız anda sayfanın yarısına kadar inmiş olsanız bile geri gelir. Animasyon yalnızca kaydırma konumuna değil, önceki kaydırma yönünüze de bağlıdır. Durum bilgili.

Başka bir sorun da kaydırma çubuklarının şekillendirilmesidir. Düzgün çalışmayan bu tarzlar ya da en azından stil sahibi değiller. Kaydırma çubuğum olarak nyan kedi istiyorsam ne yapmalıyım? Hangi tekniği seçerseniz seçin, özel bir kaydırma çubuğu oluşturmak iyi bir performans veya kolay değildir.

Amaç, tüm bunların tuhaf olması ve verimli bir şekilde uygulanması imkansız olmasıdır. Çoğu, ekranınız 90 fps, 120 fps veya daha yüksek hızlarda çalışabilse ve değerli ana iş parçacığı kare bütçenizin bir kısmını kullanabilse bile sizi 60 fps'de tutabilen etkinliklere ve/veya requestAnimationFrame'ye dayanır.

Animasyon İş Akışı, bu tür efektleri kolaylaştırmak için web'in animasyonlar yığınının özelliklerini genişletir. Başlamadan önce, animasyonlarla ilgili temel bilgileri edindiğimizden emin olalım.

Animasyonlar ve zaman çizelgeleri hakkında bir rehber

WAAPI ve Animasyon İş Akışı, animasyonları ve efektleri istediğiniz şekilde düzenleyebilmenizi sağlamak için zaman çizelgelerinden kapsamlı bir şekilde yararlanır. Bu bölümde, zaman çizelgeleri ve bunların animasyonlarla nasıl çalıştığı konusunda kısaca bilgi edinebilir veya giriş yapabilirsiniz.

Her dokümanda document.timeline var. Doküman oluşturulduğunda 0'dan başlar ve belgenin var olmaya başlamasından itibaren geçen milisaniye sayısını sayar. Bir dokümanın tüm animasyonları bu zaman çizelgesine göre çalışır.

Konuyu biraz daha somut hale getirmek için bu WAAPI snippet'ine bir göz atalım

const animation = new Animation(
  new KeyframeEffect(
    document.querySelector('#a'),
    [
      {
        transform: 'translateX(0)',
      },
      {
        transform: 'translateX(500px)',
      },
      {
        transform: 'translateY(500px)',
      },
    ],
    {
      delay: 3000,
      duration: 2000,
      iterations: 3,
    }
  ),
  document.timeline
);

animation.play();

animation.play() çağırdığımızda animasyon, başlangıç zamanı olarak zaman çizelgesinin currentTime değerini kullanır. Animasyonumuzun 3000 ms'lik bir gecikmesi vardır. Diğer bir deyişle, zaman çizelgesi "startTime"a ulaştığında animasyon başlar (veya "etkin") olur.

  • 3000. After that time, the animation engine will animate the given element from the first keyframe (translateX(0)), through all intermediate keyframes (translateX(500px)) all the way to the last keyframe (translateY(500px)) in exactly 2000ms, as prescribed by thedurationoptions. Since we have a duration of 2000ms, we will reach the middle keyframe when the timeline'scurrentTimeisstartTime + 3000 + 1000and the last keyframe atstartTime + 3000 + 2000`. Nokta, zaman çizelgesi, animasyonumuzda nerede olduğumuzu kontrol eder!

Animasyon son animasyon karesine ulaştığında, tekrar ilk animasyon karesine atlar ve animasyonun bir sonraki yinelemesini başlatır. iterations: 3 ayarlandıktan sonra bu işlem toplam 3 kez tekrarlanır. Animasyonun hiç durmamasını istiyorsak iterations: Number.POSITIVE_INFINITY yazardık. Yukarıdaki kodun sonucunu burada görebilirsiniz.

WAAPI son derece güçlüdür ve bu API'de, bu makalenin kapsamını etkileyecek yumuşak geçiş, başlangıç ofsetleri, animasyon karesi ağırlıkları ve doldurma davranışı gibi daha pek çok özellik vardır. Daha fazla bilgi edinmek isterseniz CSS İpuçları'nda CSS Animasyonları ile ilgili bu makaleyi okumanızı öneririz.

Animasyon İş Akışı Yazma

Zaman çizelgesi kavramını öğrendiğimize göre, Animasyon İş Akışı'na ve zaman çizelgelerini nasıl bozmanıza izin verdiğine bakabiliriz! Animasyon Worklet API'si yalnızca WAAPI'yi temel almaz, aynı zamanda genişletilebilir web anlamında WAAPI'nin nasıl çalıştığını açıklayan daha düşük seviye bir temel öğedir. Sözdizimleri bakımından son derece benzerdirler:

Animasyon İş Akışı WAAPI (WAAPI)
new WorkletAnimation(
  'passthrough',
  new KeyframeEffect(
    document.querySelector('#a'),
    [
      {
        transform: 'translateX(0)'
      },
      {
        transform: 'translateX(500px)'
      }
    ],
    {
      duration: 2000,
      iterations: Number.POSITIVE_INFINITY
    }
  ),
  document.timeline
).play();
      
        new Animation(

        new KeyframeEffect(
        document.querySelector('#a'),
        [
        {
        transform: 'translateX(0)'
        },
        {
        transform: 'translateX(500px)'
        }
        ],
        {
        duration: 2000,
        iterations: Number.POSITIVE_INFINITY
        }
        ),
        document.timeline
        ).play();
        

Aradaki fark ilk parametrede meydana gelir. Bu parametre, bu animasyonu yönlendiren iş uygulamasının adıdır.

Özellik algılama

Chrome, bu özelliği gönderen ilk tarayıcıdır. Bu nedenle kodunuzun AnimationWorklet öğesinin burada olmasını beklemediğinden emin olmanız gerekir. Bu nedenle, iş uygulamasını yüklemeden önce, kullanıcının tarayıcısının AnimationWorklet desteği olup olmadığını basit bir kontrolle tespit etmemiz gerekir:

if ('animationWorklet' in CSS) {
  // AnimationWorklet is supported!
}

İş uygulaması yükleniyor

İş akışları, Houdini Görev Gücü tarafından birçok yeni API'nin derlenip ölçeklendirilmesini kolaylaştırmak amacıyla ortaya atılan yeni bir kavramdır. İş parçacıklarının ayrıntılarını birazdan ele alacağız. Ancak kolaylık olması açısından bunları şimdilik ucuz ve hafif iş parçacıkları (çalışanlar gibi) olarak düşünebilirsiniz.

Animasyonu bildirmeden önce "passthrough" adlı bir iş uygulaması yüklediğimizden emin olmamız gerekir:

// index.html
await CSS.animationWorklet.addModule('passthrough-aw.js');
// ... WorkletAnimation initialization from above ...

// passthrough-aw.js
registerAnimator(
  'passthrough',
  class {
    animate(currentTime, effect) {
      effect.localTime = currentTime;
    }
  }
);

Burada ne oluyor? AnimationWorklet'nin registerAnimator() çağrısını kullanarak bir animatör olarak sınıfı kaydederiz ve sınıfa "passthrough" (geçiş) adını veriyoruz. Bu ad, yukarıdaki WorkletAnimation() oluşturucuda kullandığımız addır. Kayıt işlemi tamamlandıktan sonra, addModule() tarafından verilen söz yerine getirilecektir ve ilgili iş akışını kullanarak animasyonlar oluşturmaya başlayabiliriz.

Örneğimizin animate() yöntemi, tarayıcının oluşturmak istediği her kare için çağrılır. Bu yöntem, animasyonun zaman çizelgesinin currentTime değerinin yanı sıra şu anda işlenmekte olan efekti de geçirir. Tek bir efekt var: KeyframeEffect ve efektin localTime değerini ayarlamak için currentTime kullanıyoruz. Bu yüzden bu animatöre "geçiş" deniyor. İş akışı için bu kod kullanıldığında, WAAPI ve yukarıdaki AnimationWorklet demoda görebileceğiniz gibi tam olarak aynı şekilde davranır.

Saat

animate() yöntemimizin currentTime parametresi, WorkletAnimation() oluşturucuya ilettiğimiz zaman çizelgesinin currentTime değeridir. Önceki örnekte, bu süre o ana kadar geçmiştir. Ancak bu JavaScript kodu olduğu ve zamanı bozabildiğimiz için 💫

function remap(minIn, maxIn, minOut, maxOut, v) {
  return ((v - minIn) / (maxIn - minIn)) * (maxOut - minOut) + minOut;
}
registerAnimator(
  'sin',
  class {
    animate(currentTime, effect) {
      effect.localTime = remap(
        -1,
        1,
        0,
        2000,
        Math.sin((currentTime * 2 * Math.PI) / 2000)
      );
    }
  }
);

currentTime öğesinin Math.sin() değerini alıp bu değeri, etkimizin tanımlandığı zaman aralığı olan [0; 2000] aralığıyla yeniden eşliyoruz. Artık animasyon kareleri veya animasyonun seçenekleri değiştirilmeden animasyon çok farklı görünür. İş akışı kodu rastgele şekilde karmaşık olabilir ve hangi efektlerin hangi sırayla ve ne ölçüde oynatılacağını programlı bir şekilde tanımlamanıza olanak tanır.

Seçenekler yerine Seçenekler

Bir iş öğesini yeniden kullanmak ve sayılarını değiştirmek isteyebilirsiniz. Bu nedenle WorkletAnimation oluşturucu, iş akışına bir seçenek nesnesi iletmenize olanak tanır:

registerAnimator(
  'factor',
  class {
    constructor(options = {}) {
      this.factor = options.factor || 1;
    }
    animate(currentTime, effect) {
      effect.localTime = currentTime * this.factor;
    }
  }
);

new WorkletAnimation(
  'factor',
  new KeyframeEffect(
    document.querySelector('#b'),
    [
      /* ... same keyframes as before ... */
    ],
    {
      duration: 2000,
      iterations: Number.POSITIVE_INFINITY,
    }
  ),
  document.timeline,
  {factor: 0.5}
).play();

Bu örnekte her iki animasyon da aynı kodla farklı seçeneklerle yürütülür.

Bulunduğunuz eyaleti seçin.

Daha önce de vurguladığım gibi, animasyon iş uygulamasının çözmeyi hedeflediği temel sorunlardan biri durum bilgisi içeren animasyonlardır. Animasyon iş akışlarının durumunu muhafaza etmesine izin verilir. Bununla birlikte, iş parçacıklarının temel özelliklerinden biri, farklı bir iş parçacığına taşınabilmesi veya kaynak tasarrufu için yok edilebilmeleri ve bu da durumlarına zarar verilebilmesidir. Animasyon iş akışı, durum kaybını önlemek için bir iş akışı yok edilmeden önce çağrılan ve bir durum nesnesini döndürmek için kullanabileceğiniz bir kanca sunar. İş akışı yeniden oluşturulduğunda bu nesne oluşturucuya iletilecektir. İlk oluşturulduktan sonra bu parametre undefined olur.

registerAnimator(
  'randomspin',
  class {
    constructor(options = {}, state = {}) {
      this.direction = state.direction || (Math.random() > 0.5 ? 1 : -1);
    }
    animate(currentTime, effect) {
      // Some math to make sure that `localTime` is always > 0.
      effect.localTime = 2000 + this.direction * (currentTime % 2000);
    }
    destroy() {
      return {
        direction: this.direction,
      };
    }
  }
);

Bu demoyu her yenilediğinizde karenin döneceği yönde 50/50 bir şansınız olur. Tarayıcı iş parçasını parçalara ayırır ve farklı bir iş parçacığına taşırsa ani bir yön değişikliğine neden olabilecek başka bir oluşturma işlemi Math.random() çağrısı daha olur. Bunun olmaması için animasyonların rastgele seçilen yönü state olarak döndürür ve sağlanmışsa oluşturucuda kullanırız.

Uzay-zaman sürekliliğine giriş: ScrollTimeline

Önceki bölümde de gösterildiği gibi AnimationWorklet, zaman çizelgesinin ilerlemesinin animasyonun etkilerini nasıl etkileyeceğini programatik olarak tanımlamamıza olanak tanır. Şimdiye kadar zaman çizelgemiz, zamanı takip eden document.timeline oldu.

ScrollTimeline, yeni olasılıklar ortaya çıkarır ve zaman yerine kaydırarak animasyonlar sürdürmenizi sağlar. Bu demo için ilk "posta geçiş" iş parçamızı yeniden kullanacağız:

new WorkletAnimation(
  'passthrough',
  new KeyframeEffect(
    document.querySelector('#a'),
    [
      {
        transform: 'translateX(0)',
      },
      {
        transform: 'translateX(500px)',
      },
    ],
    {
      duration: 2000,
      fill: 'both',
    }
  ),
  new ScrollTimeline({
    scrollSource: document.querySelector('main'),
    orientation: 'vertical', // "horizontal" or "vertical".
    timeRange: 2000,
  })
).play();

document.timeline adlı meta veriyi geçmek yerine yeni bir ScrollTimeline oluşturuyoruz. ScrollTimeline zaman kullanmaz, ancak scrollSource iş parçacığının currentTime öğesini ayarlamak için kaydırma konumunu kullanır. En üste (veya sola) kadar kaydırıldığında currentTime = 0, en alta (veya sağa) kaydırıldığında currentTime değeri timeRange olarak ayarlanır. Bu demodaki kutuyu kaydırırsanız kırmızı kutunun konumunu kontrol edebilirsiniz.

Kaydırmayan bir öğeye sahip bir ScrollTimeline oluşturursanız zaman çizelgesinin currentTime değeri NaN olur. Bu nedenle, özellikle duyarlı tasarımı göz önünde bulundurduğunuzda, currentTime olarak NaN için her zaman hazırlıklı olmalısınız. Varsayılan değer olarak 0 ayarlamak genellikle mantıklıdır.

Animasyonların kaydırma konumuyla bağlanması uzun süredir arayışta olan bir şey, ancak (CSS3D ile yapılan saldırıya uğrayan geçici çözümler dışında) bu doğruluk seviyesinde hiçbir zaman gerçekten başarılamadı. Animasyon İş Akışı, bu efektlerin hem yüksek performanslı hem de basit bir şekilde uygulanmasını sağlar. Örneğin, bu demo gibi bir paralaks kaydırma efekti, kaydırmaya dayalı bir animasyon tanımlamanın artık sadece birkaç satır gerektirdiğini göstermektedir.

Gelişmiş seçenekler

İş akışları

İş akışları, izole bir kapsama ve çok küçük bir API yüzeyine sahip JavaScript bağlamlarıdır. Küçük API yüzeyi, özellikle gelişmiş cihazlarda, tarayıcıdan daha agresif optimizasyona olanak tanır. Ayrıca, iş taneleri belirli bir etkinlik döngüsüne bağlı değildir ancak gerektiğinde iş parçacıkları arasında taşınabilir. Bu, özellikle AnimationWorklet için önemlidir.

Birleştirici NSync

Bazı CSS özelliklerinde animasyonun hızlı olduğunu, bazılarının ise hızlı animasyon sunmadığını biliyor olabilirsiniz. Bazı özelliklerde animasyon için GPU'da biraz çalışma yapılması gerekirken diğerleri tarayıcının tüm belgeyi yeniden düzenlemesini zorunlu kılar.

Chrome'da (diğer pek çok tarayıcıda olduğu gibi) birleştirici adı verilen bir işlemimiz vardır. Bu sürecin görevi, katmanları ve dokuları düzenlemek ve ardından ekranı mümkün olduğunca düzenli, ideal olarak ekranın güncellenebildiği kadar hızlı (genellikle 60 Hz) güncellemek için GPU'yu kullanmaktır. Animasyon eklenen CSS özelliklerine bağlı olarak, tarayıcıda sadece birleştiricinin çalışmasını sağlarken diğer özelliklerin de düzeni çalıştırması gerekebilir. Bu, yalnızca ana iş parçacığının yapabileceği bir işlemdir. Canlandırmayı planladığınız özelliklere bağlı olarak, animasyon iş akışınız ana iş parçacığına bağlanır veya birleştiriciyle senkronize olarak ayrı bir iş parçacığında çalıştırılır.

Bileğe dokunun

GPU'nun rekabet gücü yüksek bir kaynak olması nedeniyle genellikle birden fazla sekmede paylaşılabilen yalnızca bir birleştirme işlemi olur. Oluşturucu bir şekilde engellenirse tarayıcının tamamı durur ve kullanıcı girişine yanıt vermez. Ne pahasına olursa olsun bundan kaçınılmalıdır. Peki, iş akışınız, çerçevenin oluşturulması için birleştiricinin ihtiyaç duyduğu verileri zamanında teslim edemezse ne olur?

Bu durumda, spesifikasyona göre iş öğesinin "kaymasına" izin verilir. Bestecinin gerisinde kalır ve düzenleyicinin, kare hızını yüksek tutmak için son kare verilerini yeniden kullanmasına izin verilir. Görsel olarak bu, olumsuz gibi görünür ancak en büyük fark, tarayıcının kullanıcı girişine yanıt vermeye devam etmesidir.

Sonuç

AnimationWorklet'in birçok yüzü ve web'e sağladığı yararlar vardır. Bunun en belirgin avantajları, animasyonlar üzerinde daha fazla kontrol sahibi olmak ve web'e yeni bir görsel kalite düzeyi getirmek için animasyonları yönlendirmenin yeni yollarıdır. Ancak API'lerin tasarımı, tüm yeni özelliklere aynı anda erişirken uygulamanızı olumsuz hava koşullarına karşı daha dayanıklı hale getirmenize de olanak tanır.

Animasyon İş Akışı, Canary'dedir ve Chrome 71 ile bir Kaynak Denemesi hedefliyoruz. Sizin için hazırladığımız yeni harika web deneyimlerini ve geliştirebileceğimiz noktaları öğrenmeyi sabırsızlıkla bekliyoruz. Size aynı API'yi sağlayan ancak performans yalıtımı sunmayan bir polyfill da vardır.

CSS Geçişleri ve CSS Animasyonlarının hâlâ geçerli seçenekler olduğunu ve temel animasyonlar için çok daha basit olabileceğini unutmayın. Ama daha fazlasını yapmak istiyorsanız, AnimationWorklet'te yanınızda!