1 poin oleh GN⁺ 2024-03-09 | 1 komentar | Bagikan ke WhatsApp
  • Exponential smoothing adalah teknik sederhana yang bisa digunakan secara luas untuk animasi yang perlu mengikuti nilai target dengan halus, seperti tombol toggle, kamera, elemen UI, dan volume audio
  • Rumus intinya adalah position += (target - position) * (1 - exp(- speed * dt)), dan dengan hanya posisi saat ini serta posisi target, ia merespons perubahan target secara alami
  • Pergerakan linear dan easing umum memerlukan penanganan seperti lompatan, jitter, atau antrean input saat klik di tengah animasi, input kamera cepat, atau dt besar, sedangkan exponential smoothing mengurangi kompleksitas dengan struktur yang sama
  • 1 - exp(- speed * dt) berasal dari solusi persamaan diferensial yang diselesaikan oleh rumus pembaruan proporsional; pada dt kecil hampir sama dengan rumus lama, dan pada dt besar pun menghindari overshoot
  • Secara matematis nilai target tidak pernah dicapai persis, tetapi karena presisi floating point dan batas perubahan yang bisa dideteksi pengguna, dalam animasi nyata ia berperilaku seolah sudah cukup selesai

Masalah yang terlihat pada tombol toggle

  • Posisi switch pada tombol toggle bisa dihitung sederhana seperti turned_on ? max_x : min_x, tetapi ketika status berubah, posisinya langsung berpindah seketika sehingga kurang terasa hidup
  • Animasi linear diimplementasikan dengan memperbarui posisi saat ini pada kecepatan tetap lalu membatasi rentangnya
position.x += (turned_on ? 1 : -1) * speed * dt;
position.x = clamp(position.x, min_x, max_x);
  • Gerakan dengan kecepatan tetap membuat posisi menjadi fungsi linear terhadap waktu, sehingga gerakannya bisa terlihat kaku
  • Menambahkan fungsi easing dapat membuat gerakan lebih halus
    • Cubic smoothstep klasik: 3t² - 2t³
    • Easing akar kuadrat: sqrt(t)
  • smoothstep memiliki simetri 1 - f(t) = f(1 - t), sehingga kode yang sama bisa dipakai untuk animasi maju dan mundur
  • sqrt memerlukan rumus berbeda tergantung arah
    • Saat dinyalakan: sqrt(t)
    • Saat dimatikan: 1 - sqrt(1 - t)
  • sqrt bergerak cepat di awal dan melambat dengan halus saat mendekati target, tetapi bahkan untuk toggle sederhana dengan 2 status pun tetap perlu manajemen state seperti t, arah, dan perhitungan easing
  • Jika pengguna mengeklik lagi di tengah animasi, pendekatan easing yang ada dapat menciptakan diskontinuitas berupa posisi yang tiba-tiba meloncat

Rumus exponential smoothing

  • Exponential smoothing bekerja dengan menentukan posisi target, lalu menarik posisi saat ini sedikit demi sedikit ke arah target
target = (state.value ? max_x : min_x);
position.x += (target - position.x) * (1 - exp(- dt * speed));
  • Besar pembaruan ditentukan berdasarkan selisih antara posisi saat ini dan posisi target, yaitu target - position.x
  • 1 - exp(- dt * speed) adalah koefisien interpolasi yang menentukan seberapa jauh bergerak ke arah target pada setiap frame
  • Walaupun target berubah di tengah jalan, gerakan langsung berlanjut menuju target baru tanpa waktu progres atau state arah animasi terpisah
  • Pada contoh tombol toggle, gerakannya mulai cepat dan melambat di dekat target seperti easing sqrt, tetapi masalah lompatan saat klik di tengah animasi berkurang

Keunggulan yang makin besar pada pergerakan kamera

  • Masalah yang sama juga muncul pada kamera yang bergerak di atas peta
  • Jika interpolasi berkecepatan tetap diimplementasikan secara sederhana, kode menggunakan tanda arah target per sumbu
position.x += sign(target.x - position.x) * speed * dt;
position.y += sign(target.y - position.y) * speed * dt;
  • Pada titik selesai animasi, tanda dari target - position dapat bolak-balik antara positif dan negatif sehingga terjadi jitter
  • Untuk mencegahnya, diperlukan fungsi pembaruan terpisah yang membatasi delta ke rentang max_delta
float update(float & value, float target, float max_delta)
{
    float delta = target - value;
    delta = min(delta,  max_delta);
    delta = max(delta, -max_delta);
    value += delta;
}
  • Untuk menerapkan cubic easing pada pergerakan kamera, event pergerakan yang diminta harus dimasukkan ke antrean dan diproses satu per satu, sehingga strukturnya menjadi rumit
  • Mengabaikan input pengguna selama animasi terasa sangat menjengkelkan bagi pengguna
  • Dengan exponential smoothing, pergerakan kamera bisa ditangani dengan kode yang hampir sama seperti tombol toggle
position.x += (target.x - position.x) * (1.0 - exp(- speed * dt));
position.y += (target.y - position.y) * (1.0 - exp(- speed * dt));
  • Jika pengguna mengeklik dengan cepat, selisih antara target dan posisi saat ini membesar, sehingga kamera secara alami menjadi lebih cepat

Mengapa 1 - exp(- speed * dt)?

  • Pembaruan proporsional sederhana dapat ditulis dengan rumus berikut
position += (target - position) * speed * dt;
  • Rumus ini bergerak lebih cepat ketika selisih antara target dan posisi saat ini lebih besar, dan tidak memerlukan state tambahan selain posisi saat ini dan posisi target
  • Jika ditulis dengan lerp, rumusnya menjadi sama
position = lerp(position, target, speed * dt);
  • Jika speed * dt mendekati 0, gerakannya lambat; jika mendekati 1, ia mendekati target dengan cepat
  • Jika speed * dt lebih besar dari 1, interpolasi melewati target dan terjadi overshoot
    • Pada contoh ketika speed = 220 dan dt = 1 / 125, speed * dt menjadi lebih besar dari 1 sehingga muncul jitter
    • Jika speed * dt < 2, selisih absolutnya bisa berkurang, tetapi jika speed * dt > 2, perilakunya tidak lagi berguna
  • Koefisien interpolasi bisa dibatasi dengan min(1, speed * dt), tetapi ini bukan solusi yang menangani situasi ketika dt membesar secara mulus
    • Kode yang lambat dapat membuat frame rate turun
    • Pengguna dapat berpindah ke tab atau jendela lain sehingga kode berhenti, lalu aktif kembali dengan dt beberapa detik
  • Dalam simulasi fisika, dt besar dapat dibatasi atau dipecah menjadi beberapa pembaruan, tetapi untuk animasi, pengalaman pengguna lebih baik jika kamera dan tombol tetap bergerak alami bahkan pada dt besar

Solusi dilihat dari persamaan diferensial

  • Bentuk A += B * dt umumnya merupakan penyelesaian numerik dari persamaan diferensial dA/dt = B
  • Rumus pembaruan sederhana menyelesaikan persamaan berikut
d(position) / dt = (target - position) * speed
  • Jika variabelnya ditetapkan sebagai x = position, a = target, dan c = speed, bentuknya menjadi
dx / dt = (a - x) * c
  • Jika persamaan ini diselesaikan langsung, didapat bentuk berikut
x = x0 + (a - x0) * (1 - exp(-c * t))
  • Karena itu, jika rumus yang benar pada dt kecil adalah position += (target - position) * speed * dt, maka rumus yang dapat dipakai untuk dt sembarang adalah
position += (target - position) * (1 - exp(- speed * dt));
  • Dalam ekspansi Taylor, exp(x) ≈ 1 + x, sehingga pada dt kecil 1 - exp(-speed * dt) ≈ speed * dt, sama seperti rumus sederhana sebelumnya
  • Walaupun speed * dt sangat besar, exp(-speed * dt) mendekati 0 dan 1 - exp(...) mendekati 1, sehingga bergerak stabil ke nilai yang dekat dengan target
  • Rumus yang sama juga bisa ditulis dengan lerp
position = lerp(position, target, 1 - exp(- speed * dt));
position = lerp(target, position, exp(- speed * dt));

Memilih nilai speed

  • Animasi umum mudah dipikirkan sebagai durasi, seperti “bergerak dalam 0,125 detik”, tetapi secara teknis exponential smoothing membutuhkan waktu tak hingga untuk mencapai target secara persis
  • exp(- speed * time) makin kecil seiring waktu tetapi tidak pernah menjadi 0, sehingga jika nilai awal dan target berbeda, position secara matematis juga tidak pernah sepenuhnya sama dengan target
  • Dalam praktiknya, animasi tampak selesai karena mencapai batas presisi floating point, atau karena selisih posisi kamera sudah terlalu kecil untuk terlihat oleh pengguna
  • Makna speed adalah selama waktu 1 / speed, position menjadi tepat e = 2.71828... kali lebih dekat ke target
  • Dalam penggunaan nyata, speed biasanya berada pada rentang 5..50, dan untuk kecepatan animasi linear atau cubic dengan rasa yang sama, sekitar 2 * speed pada exponential smoothing bisa terasa sesuai

Kaitan dengan pemrosesan sinyal

  • Jika mencari “exponential smoothing” atau “exponential moving average”, Anda dapat menemukan artikel Exponential smoothing di Wikipedia
  • Jika dt tetap dan target berubah pada setiap iterasi, nilai diperbarui kira-kira seperti berikut berdasarkan indeks iterasi
    • factor = 1 - exp(- speed * dt)
    • Biasanya factor ditentukan langsung sebagai nilai antara 0 dan 1
  • Exponential smoothing diskret adalah analogi diskret dari cara yang digunakan dalam animasi
  • Dalam pemrosesan sinyal, teknik ini juga dipakai karena dapat bekerja hanya dengan nilai rata-rata saat ini tanpa menyimpan daftar nilai sebelumnya atau state yang rumit
  • Pada audio digital, biasanya dt tetap sebagai kebalikan dari frekuensi sampling, yaitu 1 / freq
    • Contoh: 1/44100, 1/48000

1 komentar

 
GN⁺ 2024-03-09
Pendapat Hacker News
  • Rasanya poin intinya belum cukup dibahas di sini. Ini bukan sekadar kurva easing lain antara 0 dan 1 atau smoothstep(), melainkan metode stateless yang menangani hampir semua input secara teratur, jadi benar-benar berguna
    Kalau pernah memakai transisi CSS, masalah ini pasti terasa familier. Jika durasinya ditetapkan 400 ms, kenapa harus 400 ms? Bukankah seharusnya bergantung pada jarak yang harus ditempuh?
    Seperti yang dikatakan orang lain, exponential smoothing punya masalah: ia hanya mendekati tujuan secara asimtotik dan tidak pernah benar-benar sampai. Ada solusi yang jelas, yaitu menghentikan animasi saat langkahnya lebih kecil dari ambang batas, tetapi itu tidak elegan
    Saat memakai pendekatan serupa untuk inertial scrolling, menambahkan suku mirip gesekan ternyata berguna. Suku ini meniadakan suku eksponensial sehingga pada praktiknya berperilaku seperti kecepatan minimum. Contoh Desmos di sini: https://www.desmos.com/calculator/98ufbuzxhj

    • Mungkin ini bisa diselesaikan dengan menafsirkan exponential smoothing sebagai persamaan diferensial biasa terhadap jarak D ke tujuan. Exponential smoothing adalah pembaruan Euler dari dD/dt=-C*D, dan solusinya D(t)=A*exp(-C*t), sehingga ia mendekati 0 secara asimtotik tetapi tidak mencapainya
      Kita bisa menggantinya dengan persamaan yang menuju 0 dalam waktu hingga, misalnya dD/dt=-C*sqrt(D). Solusinya bergerak seperti setengah fungsi kuadrat lalu, begitu mencapai 0, tetap di sana. Pembaruan Euler untuk persamaan ini juga stateless seperti yang diinginkan
    • Saya tidak paham kenapa menghentikan animasi saat langkahnya turun di bawah ambang batas dianggap tidak elegan. Itu sederhana, mudah diimplementasikan dan diverifikasi, serta memberi efek visual yang diinginkan, jadi menurut saya sudah cukup rapi
    • Kalau ada masalah 400 ms pada transisi CSS, rasanya parameter kecepatan pada fungsi eksponensial juga punya masalah yang sama
  • Sebagai pengembang game, menurut saya untuk sebagian besar UI, easing tween dengan durasi yang sudah ditentukan sebelumnya lebih baik. Namun ketika ingin menghaluskan gerakan kontinu dan tak terduga yang titik awal dan akhirnya tidak jelas, jenis animasi lain ini sangat berguna
    Contohnya ketika pemain menyeret tile di atas grid dengan mouse dan ingin membuatnya snap ke grid, atau saat menggerakkan kamera seperti contoh di artikel
    Dalam kasus seperti ini, trik interpolasi eksponensial sangat berguna, tetapi belum dikenal luas. Banyak game memakai interpolasi linear yang kurang tepat, lalu mengalami masalah ketika seseorang menjalankannya di monitor 240 Hz—berbeda dengan masa ketika 60 fps adalah standar—sehingga rasa animasinya menjadi benar-benar aneh
    Karena itu saya senang melihat artikel ini. Pengetahuan yang terlalu spesifik seperti ini biasanya sulit diakses karena sering diteruskan secara lisan ala magang dari senior tim ke junior

  • Saya suka artikelnya, tetapi untuk toggle switch, saya ingin mengatakan bahwa penilaian penulis bahwa sqrt lebih baik daripada fungsi kubik itu secara objektif keliru. Jika melihat cara toggle switch sungguhan umumnya bekerja, fungsi kubik adalah pilihan yang lebih baik untuk situasi ini
    Bayangkan sakelar pemutus listrik di rumah, atau sakelar yang sering terlihat pada synth analog dan peralatan audio. Itu perangkat yang memang mengejar estetika tertentu, dan ampli gitar kecil Hughes & Kettner yang saya punya juga memiliki dua sakelar dengan rasa tekan yang menyenangkan
    Sakelar seperti ini pada awalnya memberi sedikit resistansi, lalu karena struktur pegasnya tiba-tiba menjentik ke posisi baru. Gerakan ini dimodelkan lebih baik oleh fungsi kubik daripada sqrt atau exponential smoothing
    Di luar catatan kecil itu, artikelnya sangat bagus. Ia menunjukkan dengan baik bahwa animasi yang digunakan dengan tepat, seperti fungsi easing yang sesuai, dapat meningkatkan pengalaman pengguna, tetapi jika diterapkan tanpa kehati-hatian seperti pada contoh interpolasi linear, hasilnya bisa mengganggu dan merusak pengalaman
    [0] Memang tergantung jenis toggle switch. Namun jenis yang bisa dilihat pada perangkat seperti Minimoog juga menunjukkan gerakan “menahan lalu menjentik ke posisi baru”, sehingga menyenangkan untuk dioperasikan. Sebagai catatan, ini bukan pamer perangkat; saya tidak punya Minimoog

    • Secara intuitif pun, fungsi kubik terasa lebih baik. Toggle mekanis punya sejumlah inersia, dan itu tampak dalam pendekatan kubik
  • Saya terus terkejut betapa sering trik nonlinier sederhana dapat menambah kesenangan dalam interaksi online. Dalam persepsi warna, ini juga penting untuk memahami mengapa dua warna tidak cukup bisa dibedakan oleh sebagian orang
    Yang aneh adalah manusia tidak selalu memahami akselerasi dengan baik. Jangan lari ke atas bukit dengan keyakinan bahwa api bergerak hampir dengan kecepatan konstan seperti di tanah datar. Api berakselerasi saat menanjak
    Anak-anak cepat belajar seberapa cepat bola yang dilempar bergerak menyusuri tanah, tetapi tidak selalu memahami seberapa cepat bola itu bergerak saat mengenai tangan karena gravitasi

  • Menarik bahwa sebagian besar artikel ini pada akhirnya bermuara pada easing. Sepertinya setiap generasi baru harus menemukannya kembali sendiri
    Saya ingat terpesona oleh situs web eksperimental Yugo Nakamura pada akhir 90-an. Itu salah satu situs web awal yang saya lihat yang memakai easing secara bebas untuk memberi nuansa organik: https://www.youtube.com/watch?v=NLt7Gwnt3WY

    • Saya ingat duduk di barisan depan konferensi saat Yugo presentasi, dan ketika ia menelusuri demonya, para pakar Flash lain benar-benar heboh
  • Entah kenapa saya ingin ada toggle seperti ini. Saat disentuh atau diklik dan ditahan, ia bergerak perlahan sampai sekitar 75% menuju target, lalu saat dilepas, sisanya menjentik
    Dari sudut pandang UX, saya tidak yakin apa maknanya. Mungkin berarti pengaturan baru benar-benar diterapkan atau disimpan di bagian akhir
    Atau bisa juga menjadi bagian dari dialog “Apakah Anda benar-benar yakin?”. Selama ditekan, pengaturan diterapkan, tetapi sebelum menjentik masuk ke tempatnya, bisa dibatalkan dengan Escape

    • https://jsfiddle.net/u1vybhqg/
      input:checked + .slider:active:before { transform: translateX(8px); transition: 1s; }
      input:not(:checked) + .slider:active:before { transform: translateX(18px); transition: 1s; }
    • Saya pernah melihat banyak pengguna lanjut usia mencoba menggeser toggle dan tidak terjadi apa-apa. Jadi kalau akan membuatnya seperti itu, toggle-nya juga harus benar-benar bisa digeser. Kalau tidak, lebih baik pakai checkbox saja
  • Saya suka tulisan ini. Saya pernah membuat teknik yang hampir persis sama sekitar 10 tahun lalu; waktu itu saya menyebutnya lazy-easy, dan masih memakainya sampai sekarang
    Kadang kita hanya ingin animasi yang halus tanpa perlu mengelola semua state: https://www.hailpixel.com/articles/lazy-animation-with-lazy-...

  • Tulisannya sendiri sangat bagus. Demonya tampaknya berjalan baik di Chrome, tetapi di Firefox ia berhenti saat menggulir, dan rendering halaman benar-benar terhenti

  • Sebenarnya ini pendekatan yang cukup bagus, dan juga bagus sebagai proof of concept untuk teknik animasi dan easing. Sangat mengingatkan pada Flickity
    https://metafizzy.co/blog/initial-demos/
    https://metafizzy.co/blog/math-time-resting-position/
    https://metafizzy.co/blog/particle-to-slider/
    https://metafizzy.co/blog/flickity-begins/
    Khususnya demo ini: https://codepen.io/desandro/pen/myXdej
    Teknik ini tidak hanya berguna untuk switch. Dalam praktiknya, kita juga tidak akan memakai elemen seperti ini untuk switch, dan tidak akan menjalankan 20 loop requestAnimationFrame secara paralel di seluruh situs. Kita juga tidak akan sengaja memasukkan elemen yang rusak
    Selain itu, tidak ada optimisasi untuk menghentikan rendering ketika delta sudah cukup kecil, dan kemungkinan ada puluhan penyesuaian kecil lain agar layak untuk production
    Melihat respons di sini, orang-orang tampaknya berasumsi tanpa membaca tulisannya, hanya melihat pohon tanpa melihat hutannya, atau terlalu bias dan sinis sampai membagikan penilaian yang tidak profesional demi terlihat pintar
    Sejak kapan HN jadi seperti Reddit?

    • Dalam kasus ini, banyak orang memang tidak bisa membaca tulisannya. Begitu halaman digulir sekitar sepertiganya, sebagian besar browser non-Chromium langsung macet
    • Kalau bertanya “Sejak kapan HN jadi seperti Reddit?”, cukup baca paragraf terakhir di https://news.ycombinator.com/newsguidelines.html
    • Perubahan seperti itu sudah berlangsung cukup lama, tetapi menurut pedoman HN, membicarakan hal semacam itu secara langsung dilarang, jadi orang-orang tidak mengatakannya. Aneh sekali
    • Dalam kasus ini, sinisme itu tidak tampak terlalu adil
      Soal HN yang berubah seperti Reddit, mungkin memang ada suasana sinis yang samar terhadap teknologi secara umum. Ada ketidakpercayaan yang mendasari terhadap bagaimana pemerintah dan perusahaan ingin menggunakan teknologi kuat yang sedang muncul—apakah teknologi itu dibangun bukan untuk memperbesar kebebasan individu, melainkan untuk membatasi dan mengelola individu
  • Ini hampir seperti esensi dari emotional design(https://en.wikipedia.org/wiki/Emotional_Design). Bahkan di balik animasi yang sangat kecil pun ada banyak hal yang bisa dibicarakan