CSS transition membuat perubahan nilai CSS terjadi pelan-pelan, bukan langsung loncat. Bayangin saklar lampu biasa dan saklar dimmer. Saklar biasa: ditekan, lampunya langsung terang. Dimmer: kamu tetap memutarnya ke "terang", tapi cahayanya naik bertahap. transition mengubah saklar biasa jadi dimmer: kamu tetap mengganti nilainya (lewat kelas, :hover, atribut), dan browser yang mengisi nilai-nilai di antaranya.
🧪 Tebakan cepat: Tombol "3 malam" punya
transition: all 150ms ease-out. Saat dipilih, warna latar, warna teks, dan warna garisnya berubah. Eventtransitionenddatang berapa kali? Jawabannya ada di bagian "transitionend: satu event per properti".
Menulis transition
transition adalah shorthand dari empat properti:
| Longhand | Isinya | Default |
|---|---|---|
transition-property |
properti yang dianimasikan | all |
transition-duration |
lamanya, wajib pakai satuan: 0.3s atau 300ms |
0s |
transition-timing-function |
kurva kecepatan (easing) | ease |
transition-delay |
jeda sebelum mulai | 0s |
Urutan di shorthand: properti, durasi, easing, delay. Nilai waktu pertama selalu durasi, yang kedua delay. Beberapa transisi digabung dengan koma. Ini tombol malam di halaman Bu Sri:
.nights button {
color: #1f2937;
background: #fff;
border: 1px solid #475569;
transition: background-color 150ms ease-out, color 150ms ease-out, border-color 150ms ease-out;
}
.nights button[aria-pressed="true"] {
color: #fff;
background: #15803d;
border-color: #15803d;
}
transitionditulis di aturan dasar.nights button, jadi berlaku dua arah: saat dipilih dan saat batal dipilih.- Tiap bagian yang dipisah koma punya properti, durasi, dan easing sendiri.
[aria-pressed="true"]cuma mengganti nilainya. Browser menganimasikan selisihnya selama 150 ms.
Di latihan kamu merakit string itu dengan JavaScript. Satu transisi = tiga bagian dipisah spasi, beberapa transisi = digabung dengan ", ":
| Masukan | Hasil |
|---|---|
"opacity", "0.3s", "ease-out" |
opacity 0.3s ease-out |
| dua objek: opacity 0.3s ease, transform 0.5s ease-out | opacity 0.3s ease, transform 0.5s ease-out |
| satu objek: width 1s steps(4) | width 1s steps(4), tanpa koma |
Delay: kapan transisinya selesai?
Waktu total = delay + durasi. Dengan transition: opacity 0.3s ease-in 0.1s;, 0,1 detik pertama gak ada yang bergerak, lalu opacity berubah selama 0,3 detik. Selesai di 0,4 detik setelah nilainya diganti.
Browser mengirim event di setiap tahap. Kami mengukur transition: opacity 200ms ease-out 100ms di Chromium 147, WebKit 26.4, dan Firefox 146:
| Event | Artinya | Datang di |
|---|---|---|
transitionrun |
transisi dibuat, delay mulai dihitung | 3 sampai 14 ms |
transitionstart |
delay habis, nilai mulai berubah | 104 sampai 116 ms |
transitionend |
nilai sampai di tujuan | 303 sampai 313 ms |
Tombol Pesan: hover, focus, dan ditekan
.book {
background: #15803d;
outline: 3px solid transparent;
outline-offset: 0;
transition: background-color 150ms ease-out, outline-color 150ms ease-out,
outline-offset 150ms ease-out, transform 100ms ease-out;
}
.book:hover { background: #166534; } /* hijau lebih gelap */
.book:focus-visible { outline-color: #166534; outline-offset: 3px; } /* cincin fokus keyboard */
.book:active { transform: scale(0.98); } /* sedikit mengecil saat ditekan */
- Hover: latar
#15803djadi#166534dalam 150 ms. :focus-visiblemuncul untuk pengguna keyboard: garis luar dari transparan jadi hijau dan menjauh 3 px dari tombol.:active: tombol mengecil 2% dalam 100 ms. Tekan yang terasa cepat butuh durasi lebih pendek dari hover.
Browser selalu memakai transition dari state tujuan. Kalau transition cuma ada di .book:hover, masuk ke hover beranimasi, tapi keluar dari hover langsung loncat, karena state tujuannya (.book) gak punya transition. Aturan yang sama bisa kamu manfaatkan: buka 200 ms, tutup 150 ms, seperti dialog di bawah.
Mana yang bisa di-transition
Properti yang nilainya punya "di antara" bisa diinterpolasi: angka, panjang, persen, warna, opacity, transform, box-shadow. Properti yang nilainya pilihan kata, seperti display (none atau block) atau position, disebut diskret: gak ada nilai setengah none. Jadi properti yang gak bisa dianimasikan transition biasa adalah display. visibility pengecualian: dia ikut ber-transisi, dan selama transisinya elemen tetap visible (kami ukur di tengah transisi: visible di ketiga browser).
Dua fitur baru membuat elemen bisa muncul dan hilang dengan animasi:
transition-behavior: allow-discrete(Chrome 117, Firefox 129, Safari 17.4): properti diskret ikut dalam transisi.displaysengaja dibuat tetap tampil sampai transisi keluarnya selesai. Tapi menganimasikandisplaybaru jalan di Chrome 117 dan Safari 18; Firefox belum (di Firefox 146 kami,display: nonelangsung berlaku).@starting-style(Chrome 117, Firefox 129, Safari 17.5): nilai awal untuk elemen yang baru muncul, karena elemen yang tadinyadisplay: nonegak punya nilai "sebelum".
.modal {
opacity: 0;
transform: scale(0.96);
transition: opacity 150ms ease-in, transform 150ms ease-in,
display 150ms allow-discrete, overlay 150ms allow-discrete;
}
.modal[open] {
opacity: 1;
transform: none;
transition-duration: 200ms; /* buka lebih pelan dari tutup */
transition-timing-function: ease-out;
}
@starting-style {
.modal[open] { opacity: 0; transform: scale(0.96); }
}
Kami membuka dan menutup <dialog> ini dengan showModal() dan close(). Buka beranimasi di Chromium 147, WebKit 26.4, dan Firefox 146. Tutup beranimasi di Chromium dan WebKit; di Firefox dialognya langsung hilang. overlay (dialog tetap di lapisan paling atas selama animasi tutup) baru ada di Chrome 117 ke atas; browser lain mengabaikan baris itu dengan aman.
transitionend: satu event per properti
transitionend datang sekali untuk tiap properti yang selesai berpindah. Kami memilih tombol "3 malam" dengan dua versi CSS, di ketiga browser:
transition di tombol |
transitionend yang datang |
|---|---|
all 150ms ease-out |
6: background-color, color, border-top-color, border-right-color, border-bottom-color, border-left-color |
background-color 150ms ease-out |
1: background-color |
Itu jawaban tebakannya: 6 kali. border-color adalah shorthand dari empat sisi, dan tiap sisi mendapat event sendiri. Karena itu listener selalu mengecek e.propertyName:
toast.addEventListener('transitionend', (e) => {
if (e.propertyName !== 'opacity') return; // tunggu satu properti saja
if (!toast.classList.contains('show')) toast.hidden = true;
});
transitionend gak datang kalau nilainya gak berubah (kelas baru tapi nilainya sama), kalau durasinya 0s atau transition: none, dan kalau elemennya jadi display: none di tengah jalan. Yang terakhir mengirim transitioncancel (Chrome 87, Firefox 53, Safari 13.1); kami melihatnya di ketiga browser.
⚠️ Jebakan yang sering ketemu
transitioncuma di:hover. Masuk beranimasi, keluar langsung loncat. Taruh di aturan dasarnya.transition: all. Ikut menganimasikan properti yang gak kamu niatkan (termasuk yang memicu layout) dan mengirim enamtransitionenduntuk satu tombol. Sebut propertinya satu per satu.- Durasi tanpa satuan.
transition: opacity 300diabaikan seluruhnya oleh browser. Tulis300msatau0.3s. - Dua deklarasi
transition. Yang belakangan menimpa yang depan, gak digabung. Gabungkan dalam satu deklarasi dengan koma. - Menunggu
transitionendyang gak akan datang. Gak ada perubahan,transition: none, ataudisplay: nonedi tengah jalan. Ceke.propertyName, dan siapkan jalan keluar untuk kasus itu.
TL;DR: transition = properti, durasi, easing, delay (opacity 0.3s ease-out), beberapa digabung dengan koma; durasi wajib bersatuan. Waktu total = delay + durasi: opacity 0.3s ease-in 0.1s selesai di 0,4 detik, dengan transitionrun di awal, transitionstart setelah delay, transitionend di akhir. Tulis transition di aturan dasar; browser memakai transition dari state tujuan. Yang bisa diinterpolasi: angka, panjang, warna, opacity, transform; display diskret dan gak bisa di-transition biasa. allow-discrete (Chrome 117, Firefox 129, Safari 17.4) dan @starting-style (Chrome 117, Firefox 129, Safari 17.5) membuat elemen bisa muncul dengan animasi; animasi keluar lewat display jalan di Chrome 117 dan Safari 18, belum di Firefox. transitionend datang sekali per properti (transition: all di tombol malam = 6 event), gak datang tanpa perubahan atau saat display: none (itu transitioncancel).