Easing (di CSS namanya timing function) adalah aturan yang mengubah waktu jadi kemajuan: sudah lewat sekian persen waktu, gerakannya sudah sampai sekian persen jalan. Bayangin ojek yang berhenti di depan homestay. Dia gak berhenti mendadak dari 40 km/jam ke nol; dia mengerem pelan-pelan, makin dekat makin lambat. Itu ease-out. Saat berangkat lagi, dia mulai pelan lalu makin cepat: ease-in. Yang kecepatannya rata dari awal sampai akhir, seperti robot, itu linear.
🧪 Tebakan cepat: Kartu Kamar Teras masuk dalam 300 ms dengan
ease-out, naik 16 px. Di 150 ms, tepat separuh waktunya, kartunya sudah naik berapa px? 8 px? Jawabannya ada di bagian "Menghitung cubic-bezier".
Kata kunci easing dan kurvanya
Tiap kata kunci sebenarnya singkatan dari satu kurva cubic-bezier. Kami memutar animasi 1 detik sejauh 100 px dan membaca posisinya di Chromium 147, WebKit 26.4, dan Firefox 146. Hasilnya sama persis di ketiganya:
| Easing | Sama dengan | 25% waktu | 50% waktu | 75% waktu |
|---|---|---|---|---|
linear |
cubic-bezier(0, 0, 1, 1) |
25 px | 50 px | 75 px |
ease |
cubic-bezier(0.25, 0.1, 0.25, 1) |
40,9 px | 80,2 px | 96 px |
ease-in |
cubic-bezier(0.42, 0, 1, 1) |
9,3 px | 31,5 px | 62,2 px |
ease-out |
cubic-bezier(0, 0, 0.58, 1) |
37,8 px | 68,5 px | 90,7 px |
ease-in-out |
cubic-bezier(0.42, 0, 0.58, 1) |
12,9 px | 50 px | 87,1 px |
ease-in: mulai lambat lalu cepat. Di separuh waktu baru 31,5% jalan.ease-out: mulai cepat lalu lambat. Di separuh waktu sudah 68,5% jalan.ease(default) miripease-outyang awalnya sedikit lebih pelan.
Menghitung cubic-bezier
Kurva cubic-bezier(x1, y1, x2, y2) punya empat titik: P0 = (0, 0), P1 = (x1, y1), P2 = (x2, y2), P3 = (1, 1). Sumbu x adalah waktu, sumbu y adalah kemajuan. Sebuah parameter s berjalan dari 0 ke 1 menyusuri kurvanya:
- x(s) = 3(1 − s)² · s · x1 + 3(1 − s) · s² · x2 + s³
- y(s) = 3(1 − s)² · s · y1 + 3(1 − s) · s² · y2 + s³
Contoh ease-out = cubic-bezier(0, 0, 0.58, 1) di s = 0,5:
- x = 3 · 0,25 · 0,5 · 0 + 3 · 0,5 · 0,25 · 0,58 + 0,125 = 0 + 0,2175 + 0,125 = 0,3425
- y = 3 · 0,25 · 0,5 · 0 + 3 · 0,5 · 0,25 · 1 + 0,125 = 0 + 0,375 + 0,125 = 0,5
Artinya: di 34,25% waktu, gerakannya sudah 50% jalan. Browser bekerja terbalik: diberi waktu t, dia mencari s yang x(s)-nya sama dengan t, lalu menghitung y(s).
| Waktu t | s yang dicari | Kemajuan y | Kartu (dari 16 px) |
|---|---|---|---|
| 0,25 (75 ms) | 0,418 | 0,378 | naik 6 px |
| 0,5 (150 ms) | 0,626 | 0,685 | naik 11 px |
| 0,75 (225 ms) | 0,811 | 0,906 | naik 14,5 px |
Itu jawaban tebakannya: di 150 ms kartunya sudah naik 11 px dari 16, bukan 8. Sisa 5 px dijalani pelan di 150 ms terakhir.
function cubicBezier(x1, y1, x2, y2) {
const x = (s) => 3 * (1 - s) ** 2 * s * x1 + 3 * (1 - s) * s ** 2 * x2 + s ** 3;
const y = (s) => 3 * (1 - s) ** 2 * s * y1 + 3 * (1 - s) * s ** 2 * y2 + s ** 3;
return (t) => {
let lo = 0, hi = 1;
for (let i = 0; i < 30; i++) { // cari s yang x(s) = t, dengan membagi dua
const mid = (lo + hi) / 2;
if (x(mid) < t) lo = mid; else hi = mid;
}
return y((lo + hi) / 2);
};
}
const easeOut = cubicBezier(0, 0, 0.58, 1);
console.log((16 * easeOut(0.5)).toFixed(1)); // "11.0": px yang sudah dinaiki di 150 ms
- Pencarian bagi dua bisa dipakai karena x(s) selalu naik kalau x1 dan x2 ada di antara 0 dan 1.
- Karena itu CSS menolak x di luar 0 sampai 1: waktu gak boleh mundur.
cubic-bezier(1.2, 0, 0.5, 1)membuat seluruh deklarasinya diabaikan.
Melewati target: y di luar 0 sampai 1
y boleh berapa saja. cubic-bezier(0.68, -0.55, 0.27, 1.55): y1 negatif membuat gerakannya mundur dulu, y2 di atas 1 membuatnya melewati tujuan lalu kembali. Di ketiga browser: paling mundur −9,3% di 19,8% waktu, paling jauh 109,3% di 78,6% waktu, dan berakhir tepat 100%. Galeri foto yang digeser 300 px dengan kurva ini mundur 28 px dulu, lalu kelewatan 28 px sebelum berhenti. Efek pantul seperti ini terasa main-main; pakai sesekali, jangan di tombol yang ditekan tiap hari.
steps(): loncat per langkah
steps(n, jump-...) memotong animasi jadi n anak tangga, tanpa gerak di antaranya. Istilah jump-* (Chrome 77, Firefox 65, Safari 14) menentukan di mana loncatannya. Kemajuan steps(4, ...) yang kami ukur di ketiga browser:
| Fungsi | 0% | 10% | 25% | 50% | 99,9% | 100% |
|---|---|---|---|---|---|---|
steps(4) = steps(4, jump-end) |
0 | 0 | 25 | 50 | 75 | 100 |
steps(4, jump-start) |
25 | 25 | 50 | 75 | 100 | 100 |
steps(4, jump-none) |
0 | 0 | 33,3 | 66,7 | 100 | 100 |
steps(4, jump-both) |
20 | 20 | 40 | 60 | 80 | 100 |
Titik-titik "Memesan..." di tombol Pesan muncul satu per satu: kosong, satu, dua, tiga titik, masing-masing 300 ms.
.dots { display: inline-block; animation: dots 1200ms steps(4, jump-none) infinite; }
@keyframes dots {
from { clip-path: inset(0 100% 0 0); } /* semua titik tertutup */
to { clip-path: inset(0 0 0 0); } /* tiga titik terlihat */
}
Dengan jump-none, 4 langkah = 0, 1/3, 2/3, 1: kosong sampai tiga titik. Dengan steps(4) biasa (jump-end), langkah terakhir (tiga titik) baru tercapai tepat di akhir, dan di animasi infinite dia gak pernah terlihat.
linear(): kurva dari titik-titik
linear() (Chrome 113, Firefox 112, Safari 17.2) menyambung titik-titik dengan garis lurus. linear(0, 0.25 50%, 1) artinya: di 50% waktu, kemajuan 25%. Kami mengukurnya: 12,5% di 25% waktu, 25% di 50%, 62,5% di 75%. Gunanya meniru kurva yang gak bisa dibuat cubic-bezier, misalnya pegas yang melewati target sekali lalu mengendap:
.strip { transition: transform 300ms linear(0, 1.1 60%, 0.97 80%, 1); }
Di 60% waktu foto sudah 110% jalan (kelewatan 10%), di 80% kembali ke 97%, dan di 100% diam di tempat.
Masuk pakai ease-out, keluar pakai ease-in
Elemen yang masuk butuh respons cepat lalu mendarat pelan: orang langsung melihat ada yang datang, dan matanya bisa mengikuti sampai berhenti. Elemen yang keluar mulai pelan lalu makin cepat menghilang: gak ada yang menunggunya, jadi dia gak perlu mendarat. Dokumentasi motion Material Components (Android) menulis kurvanya dengan angka:
| Untuk | Nama | Nilai |
|---|---|---|
| masuk ke layar | emphasized decelerate | cubic-bezier(0.05, 0.7, 0.1, 1) |
| keluar dari layar | emphasized accelerate | cubic-bezier(0.3, 0, 0.8, 0.15) |
| mulai dan selesai di layar | standard | cubic-bezier(0.2, 0, 0, 1) |
Emphasized decelerate sudah 62,1% jalan di 10% waktu; emphasized accelerate baru 15,4% di separuh waktu. Halaman Bu Sri memakai ease-out untuk yang masuk dan ease-in untuk yang keluar, versi yang lebih lembut dari ide yang sama.
Durasi: makin jauh, makin lama
Dokumentasi yang sama: "duration should increase as the area/traversal of an animation increases". Durasi makin panjang kalau area atau jarak tempuhnya makin besar. Token durasi Material 3 (md.sys.motion.duration.*) berjarak 50 ms: short1 sampai short4 = 50, 100, 150, 200 ms; medium1 sampai medium4 = 250 sampai 400 ms; long1 sampai long4 = 450 sampai 600 ms. Durasi halaman Bu Sri pas dengan token itu:
| Momen | Jarak atau area | Token | Durasi |
|---|---|---|---|
| Tombol malam ditekan | mengecil 4% | short2 |
100 ms |
| Warna tombol malam | gak bergerak | short3 |
150 ms |
| Toast dan dialog masuk | sekitar 60 px, area kecil | short4 |
200 ms |
| Kartu kamar masuk dan galeri foto | satu kartu, satu foto selebar layar | medium2 |
300 ms |
Yang keluar dibuat lebih pendek dari yang masuk (toast 150 ms keluar, 200 ms masuk), karena orang sudah gak menunggunya.
⚠️ Jebakan yang sering ketemu
ease-inuntuk elemen yang masuk. Di 75 ms pertama kartu baru naik 1,5 px dari 16: terasa telat merespons. Pakaiease-out.linearuntuk gerak. Terasa mekanis. Simpan untuk yang memang rata: spinner yang berputar, bar hitung mundur.- x di luar 0 sampai 1.
cubic-bezier(1.2, 0, 0.5, 1)gak valid, dan seluruh deklarasitransitionikut diabaikan. steps(4)di animasi berulang. Langkah terakhirnya gak pernah terlihat. Pakaijump-nonekalau awal dan akhir harus sama-sama tampil.- Pantulan di mana-mana. Overshoot di tombol yang ditekan puluhan kali sehari cepat terasa melelahkan.
TL;DR: Easing memetakan waktu ke kemajuan. ease-in mulai lambat lalu cepat (31,5% di separuh waktu), ease-out mulai cepat lalu lambat (68,5% di separuh waktu), linear rata. cubic-bezier(x1, y1, x2, y2): x(s) dan y(s) dihitung dari empat titik; untuk waktu t, cari s yang x(s) = t lalu hitung y(s); ease-out di s = 0,5 memberi (0,3425; 0,5), dan kartu 16 px sudah naik 11 px di 150 ms. x wajib 0 sampai 1, y boleh di luarnya: cubic-bezier(0.68, -0.55, 0.27, 1.55) mundur 9,3% lalu melewati target 9,3%. steps(n, jump-*) meloncat per langkah (jump-none menampilkan awal dan akhir), linear() menyambung titik-titik (Chrome 113, Firefox 112, Safari 17.2). Masuk pakai deceleration (ease-out, emphasized decelerate cubic-bezier(0.05, 0.7, 0.1, 1)), keluar pakai acceleration (ease-in, emphasized accelerate cubic-bezier(0.3, 0, 0.8, 0.15)). Durasi naik bersama jarak: 100, 150, 200, 300 ms = token M3 short2, short3, short4, medium2.