Easing & Timing Functions — Web Animation

Easing & Timing Functions Timing function (atau easing) menentukan kurva kecepatan animasi — apakah animasi mulai pelan lalu cepat, atau cepat lalu pelan…

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

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:

Contoh ease-out = cubic-bezier(0, 0, 0.58, 1) di s = 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

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

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.

Yang akan kamu pelajari