@keyframes adalah naskah sebuah animasi: daftar keadaan dengan tanda waktunya, dari 0% sampai 100%. Properti animation memutar naskah itu di satu elemen. Bayangin koreografi tari: gerakannya ditulis sekali dengan hitungan ("hitungan 1 tangan naik, hitungan 4 turun"), lalu tiap penari membawakannya dengan tempo, jumlah ulangan, dan arahnya sendiri. Bedanya dengan transition: transition butuh perubahan nilai untuk mulai dan cuma punya dua titik, sedangkan animation jalan sendiri begitu aturannya berlaku dan boleh punya banyak titik.
🧪 Tebakan cepat: Toast "Pesanan terkirim" punya gaya dasar
opacity: 0(tersembunyi). Saat muncul, dia diberianimation: toast-in 200ms ease-out, dantoast-inberakhir diopacity: 1. Setelah 200 ms, opacity-nya berapa? Jawabannya ada di bagian "fill-mode: apa yang tersisa setelah selesai".
Menulis @keyframes dan animation
@keyframes toast-in {
from { opacity: 0; transform: translateY(120%); } /* sama dengan 0% */
to { opacity: 1; transform: none; } /* sama dengan 100% */
}
.toast.show {
animation: toast-in 200ms ease-out forwards;
}
@keyframes toast-in { ... }memberi nama naskahnya. Di dalamnya, tiap persen adalah satu titik waktu;from=0%,to=100%.animationadalah shorthand. Nilai waktu pertama durasi, yang kedua delay; sisanya boleh urutan apa saja, tapi biasakan nama, durasi, easing, jumlah ulangan.
| Longhand | Di toast | Default |
|---|---|---|
animation-name |
toast-in |
none |
animation-duration |
200ms |
0s |
animation-timing-function |
ease-out |
ease |
animation-delay |
0s |
0s |
animation-iteration-count |
1 (angka, atau infinite) |
1 |
animation-direction |
normal |
normal |
animation-fill-mode |
forwards |
none |
animation-play-state |
running |
running |
Di latihan kamu merakit keduanya sebagai teks satu baris. Tiap langkah jadi persen% { properti: nilai; }, semua langkah digabung spasi, lalu dibungkus @keyframes nama { ... }:
| Masukan | Hasil |
|---|---|
fadeIn, langkah 0 { opacity: 0 } dan 100 { opacity: 1 } |
@keyframes fadeIn { 0% { opacity: 0; } 100% { opacity: 1; } } |
pop, langkah 0 { transform: "scale(1)", opacity: 0.5 } |
@keyframes pop { 0% { transform: scale(1); opacity: 0.5; } } |
fadeIn, 0.5s, ease-out, 1 |
fadeIn 0.5s ease-out 1 |
spin, 1s, linear, infinite |
spin 1s linear infinite |
Satu langkah boleh punya beberapa properti, dan pengulangan tanpa batas ditulis dengan kata infinite, bukan angka.
Titik di tengah: 0%, 50%, 100%
Naskah boleh punya titik di mana saja. Panah kecil "Lihat foto lain" di galeri Kamar Teras memantul tiga kali lalu diam:
@keyframes bounce {
0%, 100% { transform: translateY(0); } /* awal dan akhir di tempat */
50% { transform: translateY(-30px); } /* tengah: naik 30 px */
}
.hint { animation: bounce 600ms ease-in-out 3; }
0%, 100%berbagi satu blok: mulai dan selesai di posisi yang sama.50%adalah puncaknya: di 300 ms dari 600 ms, panah ada 30 px di atas.- Easing berlaku untuk tiap potongan di antara dua titik:
ease-in-outdipakai dari 0% ke 50%, lalu lagi dari 50% ke 100%. 3= tiga kali, total 1.800 ms, lalu berhenti supaya gak mengganggu terus.
fill-mode: apa yang tersisa setelah selesai
Animasi cuma menguasai nilainya selama dia jalan. Sebelum mulai dan setelah selesai, elemen kembali ke gaya dasarnya, kecuali animation-fill-mode bilang lain:
fill-mode |
Selama delay | Setelah selesai |
|---|---|---|
none |
gaya dasar | gaya dasar |
forwards |
gaya dasar | nilai titik terakhir |
backwards |
nilai titik pertama | gaya dasar |
both |
nilai titik pertama | nilai titik terakhir |
Toast tadi gaya dasarnya opacity: 0. Tanpa forwards, dia naik dan muncul selama 200 ms, lalu langsung kembali ke opacity: 0: hilang lagi. Itu jawaban tebakannya: 0. Kami mengujinya di Chromium 147, WebKit 26.4, dan Firefox 146: none berakhir di 0, forwards di 1. Untuk @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } dengan animation: fadeIn 2s forwards, opacity setelah selesai = 1, nilai to.
Toast lengkap: masuk, tunggu, keluar
Toast di halaman Bu Sri masuk 200 ms, tampil 5 detik dengan bar hitung mundur, lalu keluar 150 ms. Bar-nya berhenti selama penunjuk ada di atas toast, supaya orang sempat membaca.
.toast { opacity: 0; transform: translateY(120%); } /* dasar: tersembunyi di bawah */
.toast.show { animation: toast-in 200ms ease-out forwards; }
.toast.hide { animation: toast-out 150ms ease-in forwards; }
.toast.show .bar, .toast.hide .bar { /* nama sama: bar gak mulai ulang saat keluar */
transform-origin: left;
animation: countdown 5s linear forwards;
}
.toast:hover .bar { animation-play-state: paused; } /* bar berhenti selama dibaca */
@keyframes toast-in { to { opacity: 1; transform: none; } }
@keyframes toast-out { from { opacity: 1; transform: none; } to { opacity: 0; transform: translateY(120%); } }
@keyframes countdown { to { transform: scaleX(0); } }
@keyframes fade-in { to { opacity: 1; } }
@keyframes fade-out { from { opacity: 1; } to { opacity: 0; } }
@media (prefers-reduced-motion: reduce) {
.toast { transform: none; }
.toast.show { animation: fade-in 150ms linear forwards; }
.toast.hide { animation: fade-out 150ms linear forwards; }
}
toast.addEventListener('animationend', (e) => {
if (e.animationName === 'countdown') toast.className = 'toast hide'; // 5 detik habis
if (e.animationName === 'toast-out') toast.hidden = true; // sudah turun
});
| Waktu | Yang terjadi | Event |
|---|---|---|
| 0 ms | kelas show: toast naik, bar mulai menyusut |
animationstart toast-in, animationstart countdown |
| 200 ms | toast sampai, tetap terlihat karena forwards |
animationend toast-in |
| 5.000 ms | bar habis | animationend countdown, kelas jadi hide |
| 5.150 ms | toast sudah turun | animationend toast-out, hidden = true |
animationenddari bar ikut naik (bubble) ke toast, jadi satu listener cukup, dane.animationNamemembedakannya.- Selama
animation-play-state: paused, waktu animasi berhenti. Kami menjeda bar 300 ms:animationend-nya datang sekitar 300 ms lebih lambat. - Di mode kurangi gerakan, toast cuma memudar; bar hitung mundur tetap ada karena dia memberi informasi.
Ulangan, arah, dan event
animation-iteration-count: 3memutar naskah 3 kali;infinitetanpa henti.animation-direction:normal(0% ke 100%),reverse(100% ke 0%),alternate(maju, mundur, maju),alternate-reverse.- Event-nya:
animationstartsaat mulai,animationiterationdi tiap batas ulangan,animationendsaat selesai. Animasi 100 ms × 3 di ketiga browser: start di 0 ms, iteration di 100 dan 200 ms, end di 300 ms. Denganinfinite,animationendgak pernah datang. - Kalau elemennya diberi
display: nonedi tengah jalan, yang datanganimationcancel(Chrome 83, Firefox 54, Safari 13.1), bukananimationend. - Memberi kelas yang sama lagi gak memutar ulang animasinya. Hapus kelasnya, paksa browser mencatatnya (misalnya membaca
el.offsetWidth), baru tambahkan lagi.
⚠️ Jebakan yang sering ketemu
- Lupa
forwards. Elemen yang gaya dasarnya tersembunyi muncul sebentar lalu hilang lagi. forwardsmenahan nilainya. Setelah toast-in selesai denganforwards, menggantiopacitytoast lewat CSS ataustylegak berpengaruh, karena nilai animasi menang. Kami setstyle.opacity = '0.3', opacity-nya tetap 1. Ganti animasinya (seperti kelashide), atau hapus animasinya.- Lupa durasi.
animation: fadeInberdurasi0s:animationstartdananimationendlangsung datang, dan gak ada yang terlihat bergerak. - Nama salah ketik.
animation: toast-ni 200msgak memunculkan error apa pun; elemennya diam saja. - Menunggu
animationenddari animasiinfinite. Gak akan datang. Hentikan dengan menghapus animasinya.
TL;DR: @keyframes nama { 0% { ... } 50% { ... } 100% { ... } } menulis naskah (from = 0%, to = 100%, satu titik boleh banyak properti, 0%, 100% boleh berbagi blok). animation: nama durasi easing ulangan memutarnya (fadeIn 0.5s ease-out 1, spin 1s linear infinite); waktu pertama durasi, kedua delay; easing berlaku per potongan. fill-mode: none kembali ke gaya dasar, forwards menahan titik terakhir (fadeIn 2s forwards berakhir di opacity 1), backwards memakai titik pertama selama delay, both keduanya. play-state: paused menghentikan waktunya. Event: animationstart, animationiteration per batas ulangan, animationend (gak datang untuk infinite), dan animationcancel saat display: none. Untuk memutar ulang: hapus kelas, paksa reflow, tambahkan lagi.