Web Animations API (WAAPI) — Web Animation

Web Animations API (WAAPI) Web Animations API adalah animasi native JavaScript yang berjalan di compositor thread (sama seperti CSS animation), tapi kamu…

Web Animations API (WAAPI) adalah @keyframes yang ditulis dari JavaScript lewat element.animate(). Bedanya dengan CSS: dia mengembalikan sebuah object Animation, semacam remote pemutar video. Kamu bisa menjeda, melanjutkan, memutar mundur, melompat ke detik tertentu, mengubah kecepatan, dan minta diberi tahu saat filmnya selesai.

🧪 Tebakan cepat: Toast "Pesanan terkirim" masuk dengan fill: 'forwards'. Lalu kode lain menulis toast.style.opacity = '0' untuk menyembunyikannya. Toast-nya hilang? Jawabannya ada di bagian "fill, commitStyles, dan persist".

element.animate(keyframes, options)

const toast = document.querySelector('.toast');

const masuk = toast.animate(
  [
    { opacity: 0, transform: 'translateY(16px)' },  // keyframe awal (from)
    { opacity: 1, transform: 'none' },              // keyframe akhir (to)
  ],
  { duration: 200, easing: 'ease-out' }             // options
);

Di latihan, kamu menyusun dua argumen itu sebagai satu object { keyframes, options } lalu mencetaknya sebagai JSON. Fade-in 500 ms tercetak seperti ini:

const fade = {
  keyframes: [{ opacity: 0 }, { opacity: 1 }],
  options: { duration: 500, easing: 'ease-out', fill: 'forwards' },
};
console.log(JSON.stringify(fade.keyframes)); // [{"opacity":0},{"opacity":1}]
console.log(JSON.stringify(fade.options));   // {"duration":500,"easing":"ease-out","fill":"forwards"}
element.animate(fade.keyframes, fade.options);

fill: 'forwards' di sini menahan opacity 1 setelah fade selesai. Kapan itu tepat dan kapan jadi masalah, dibahas di bagian fill di bawah.

Animation: remotenya

Panggilan Yang terjadi
anim.pause() berhenti di tempat; playState jadi 'paused'
anim.play() lanjut dari currentTime terakhir
anim.reverse() balik arah dari posisi sekarang
anim.finish() lompat ke akhir; playState langsung 'finished'
anim.cancel() buang efeknya, elemen kembali ke gaya CSS-nya; playState 'idle', currentTime null
anim.currentTime = 2500 lompat ke milidetik 2.500
anim.updatePlaybackRate(2) dua kali lebih cepat (atau set anim.playbackRate)

Kami mengujinya pada bar 5 detik di toast. Dijeda di 300 ms, currentTime-nya tetap 300 setelah 300 ms berikutnya. Dilanjutkan, 200 ms kemudian dia di 501. Waktu yang berhenti benar-benar berhenti.

Menunggu selesai: finished

anim.finished adalah Promise yang selesai saat animasinya selesai, termasuk lewat finish(). Pakai await di dalam async function:

await masuk.finished;  // baru lanjut setelah 200 ms masuknya selesai

Lebih tepat dari setTimeout(..., 200): timeout gak tahu kalau animasinya dijeda, dipercepat, atau dimundurkan. Dua hal yang perlu kamu tahu:

Toast lengkap, jeda saat disorot

Toast "Pesanan terkirim. Bu Sri akan konfirmasi lewat WhatsApp." masuk 200 ms, tampil 5 detik dengan bar yang menyusut, berhenti selama disorot kursor, lalu keluar 150 ms:

async function showToast() {
  const toast = document.querySelector('.toast');   // <div class="toast" role="status">
  const bar = toast.querySelector('.bar');
  toast.hidden = false;
  toast.animate(
    [{ opacity: 0, transform: 'translateY(16px)' }, { opacity: 1, transform: 'none' }],
    { duration: 200, easing: 'ease-out' }
  );
  const timer = bar.animate(
    [{ transform: 'scaleX(1)' }, { transform: 'scaleX(0)' }],
    { duration: 5000, easing: 'linear' }
  );
  toast.onpointerenter = () => timer.pause();   // disorot: waktunya berhenti
  toast.onpointerleave = () => timer.play();    // lanjut dari sisa waktunya
  try {
    await timer.finished;                       // 5 detik tampil, tanpa jeda
  } catch {
    toast.hidden = true;                        // dibatalkan dengan cancel(): sembunyikan langsung
    return;
  }
  const keluar = toast.animate(
    [{ opacity: 1, transform: 'none' }, { opacity: 0, transform: 'translateY(16px)' }],
    { duration: 150, easing: 'ease-in', fill: 'forwards' }
  );
  await keluar.finished;
  toast.hidden = true;
  keluar.cancel();                              // buang efek forwards-nya
}

fill, commitStyles, dan persist

Tanpa fill, efek animasi hilang begitu selesai, dan elemen kembali ke gaya CSS-nya. Kalau CSS toast menulis opacity: 0 sebagai keadaan awal, toast-nya menghilang lagi tepat setelah masuk (kami ukur: opacity kembali 0).

fill: 'forwards' menahan frame terakhir, tapi animasinya tetap menempel di elemen dan menang dari style inline. Kami menulis toast.style.opacity = '0' setelah animasi forwards selesai: opacity yang dihitung browser tetap 1 di Chromium 147, WebKit 26.4, dan Firefox 146. Itu jawaban tebakannya: toast-nya gak hilang. toast.getAnimations().length masih 1.

Jalan keluarnya, tulis hasil akhir ke style inline lalu lepas animasinya:

masuk.commitStyles();  // style="opacity: 1; transform: translateY(0px);"
masuk.cancel();        // animasinya dilepas, getAnimations().length jadi 0
toast.style.opacity = '0';  // sekarang berhasil

Browser juga membersihkan sendiri. Kami menganimasikan translateX tiga kali berturut-turut dengan fill: 'forwards': dua yang lama otomatis dibuang (replaceState 'removed'), cuma yang terakhir tersisa. Kalau kamu memang butuh semuanya tetap ada, panggil anim.persist() (replaceState jadi 'persisted').

Aturan praktisnya: taruh keadaan akhir di CSS dan animasikan tanpa fill, atau pakai commitStyles() lalu cancel().

🤔 getAnimations() dan composite

element.getAnimations() mengembalikan semua animasi yang sedang aktif di elemen itu, termasuk CSS transition (object CSSTransition) dan CSS animation (CSSAnimation). Jadi kamu bisa menunggu transition CSS dari JavaScript:

await Promise.all(card.getAnimations().map((a) => a.finished));

Option composite mengatur cara keyframes digabung dengan nilai yang sudah ada. Default-nya 'replace': menimpa. Kartu dengan transform: translateX(10px) lalu dianimasikan scale(0.96) saat ditekan kehilangan geseran 10 px-nya (kami ukur: matrix(0.96, 0, 0, 0.96, 0, 0)). Dengan composite: 'add', skalanya ditambahkan: matrix(0.96, 0, 0, 0.96, 10, 0). Didukung Chrome 84, Firefox 80, Safari 16.

WAAPI, CSS, atau requestAnimationFrame?

element.animate() ada sejak Chrome 36, Firefox 48, Safari 13.1; finished, commitStyles(), dan persist() sejak Chrome 84, Firefox 75 (finished Firefox 63), Safari 13.1. Aman dipakai di semua browser modern.

⚠️ Jebakan yang sering ketemu

TL;DR: element.animate(keyframes, options) = @keyframes dari JavaScript: keyframes array object camelCase, options { duration: ms, easing, fill, ... }, dan hasilnya object Animation yang langsung jalan. Remotenya: pause(), play(), reverse(), finish(), cancel(), currentTime, playbackRate. await anim.finished menunggu sampai selesai (dan ditolak AbortError saat cancel()); toast Bu Sri menunggu bar 5 detiknya, yang dijeda saat disorot. fill: 'forwards' menahan frame terakhir tapi menimpa style inline (style.opacity = '0' gak berefek); commitStyles() + cancel() memindahkannya ke style inline, browser membuang animasi forwards yang tertimpa, dan persist() mencegahnya. getAnimations() ikut menemukan CSS transition, composite: 'add' menambah ke transform yang ada. Pakai WAAPI untuk animasi UI yang butuh kontrol; rAF untuk fisika dan canvas.

Yang akan kamu pelajari