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 menulistoast.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
);
- Argumen pertama, keyframes: array berisi satu object per keyframe. Nama properti ditulis camelCase (
backgroundColor, bukanbackground-color), nilainya string seperti di CSS. Bentuk singkatnya juga boleh:{ opacity: [0, 1] }. - Argumen kedua, options:
durationdalam milidetik (angka),easing(string CSS), plusdelay,iterations(Infinityuntuk terus berulang),direction, danfill. - Animasinya langsung jalan, dan
animate()mengembalikan objectAnimation. Tepat setelah dipanggil,playState-nya'running'danpending-nyatruesampai frame pertama.
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:
- Setelah
finish(),playStatelangsung'finished', tapi Promise-nya selesai sedikit kemudian (asinkron), baru disusul eventfinish. Urutan itu sama di tiga browser. cancel()membuatfinishedditolak (reject) denganAbortError. Pesannya beda tiap browser: Chromium "The user aborted a request.", WebKit dan Firefox "The operation was aborted.".awaittanpatry/catchjadi error yang gak tertangani.
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
}
- Masuk tanpa
fill: gaya akhirnya (opacity: 1) sudah ada di CSS toast, jadi gak perlu ditahan. - Bar 5 detik juga animasi WAAPI. "Tampil 5 detik" = menunggu
timer.finished, jadi jeda saat disorot otomatis memperpanjang waktunya. Kalau toast-nya punya tombol, jeda juga saat fokus keyboard masuk (focusin) dan lanjut saat keluar (focusout). - Keluar memakai
fill: 'forwards'supaya gak berkedip sebelumhidden, lalucancel()membuang efeknya.
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?
- CSS (transition,
@keyframes) untuk perubahan yang dipicu CSS: hover, kelas, atribut. - WAAPI saat butuh kontrol dari JavaScript: pause, reverse, lompat waktu, menunggu
finished, atau nilai keyframe dari data. Untuktransformdanopacity, dia tetap jalan di compositor thread seperti CSS: saat main thread sibuk 500 ms, WAAPI menggambar 60 frame, CSS transition 61, rAF 1. - requestAnimationFrame saat kamu menghitung sendiri tiap frame: fisika, canvas.
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
- Durasi ditulis detik.
animate(kf, 0.3)berarti 0,3 milidetik; kami cekgetTiming().durationbernilai0.3. Tulis300. fill: 'forwards'di mana-mana. Animasinya terus menempel dan menimpa style inline. Pakai CSS sebagai keadaan akhir, ataucommitStyles()+cancel().- Nama properti pakai tanda minus.
{ 'background-color': ... }diabaikan diam-diam di tiga browser. TulisbackgroundColor. await anim.finishedtanpatry/catch.cancel()menolaknya denganAbortError.setTimeoutuntuk menunggu animasi. Gak ikut berhenti saat animasinya dijeda.
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.