Lompat ke konten
Raizel/atelier
← Semua catatan
Frontend2 menit baca

Animasi yang selesai sebelum ada yang melihatnya

Delapan elemen ber-stagger di kartu unggulan situs ini tidak pernah terlihat oleh satu pengunjung pun selama berminggu-minggu. Tidak ada error, tidak ada yang rusak — waktunya saja yang salah.

  • CSS
  • IntersectionObserver
  • Tailwind
  • Aksesibilitas

Kartu unggulan di section Work punya visual pipeline: lima bar gradien dan tiga label yang muncul berurutan dengan jeda 90ms. Di localhost, tempat saya menatapnya sambil menulis, semuanya bekerja sempurna. Di situs sebenarnya, tidak ada yang pernah melihatnya.

Penyebabnya: animasi CSS mulai berjalan begitu elemennya ada di DOM, bukan begitu elemennya terlihat. Kartu itu berada beberapa layar di bawah viewport dan dibungkus komponen reveal berbasis IntersectionObserver. Jadi urutannya: halaman mount, animasi 0.7s berjalan sampai habis di dalam wrapper yang masih transparan, dan ketika pengunjung akhirnya scroll sampai ke sana, yang tampil adalah keadaan akhir. Stagger-nya sudah lewat sekitar sepuluh detik sebelumnya.

Perbaikan: animasi menunggu tanda dari induknya

Alih-alih memasang animasi langsung, wrapper reveal mengekspos statusnya sebagai atribut data, dan anak-anaknya hanya menganimasi ketika atribut itu bernilai true. Tidak butuh state tambahan dan tidak ada JS baru yang dikirim ke klien:

tsx
// sebelum — mulai saat mount, jauh sebelum terlihat
<span
  className="animate-fade-up"
  style={{ animationDelay: `${index * 90}ms` }}
/>

// sesudah — menunggu Reveal menandai dirinya terlihat
<span
  className="opacity-0 group-data-[revealed=true]/reveal:animate-fade-up motion-reduce:opacity-100"
  style={{ animationDelay: `${index * 90}ms` }}
/>

Dua detail di potongan itu yang bukan kosmetik. Pertama, opacity-0 sebagai keadaan awal: tanpa itu, elemen sudah terlihat sebelum animasinya mulai, dan fade-up berubah menjadi kedipan. Kedua, motion-reduce:opacity-100 — pengguna yang mematikan animasi tidak boleh berakhir dengan elemen yang tidak pernah muncul, dan ini kegagalan paling umum pada pola 'sembunyikan lalu animasikan'.

Group bernama, dan alasannya bukan gaya penulisan

Wrapper-nya memakai group/reveal, bukan group biasa. Kartu-kartu di dalamnya memakai group anonim untuk efek spotlight yang mengikuti kursor, dan varian group-hover di Tailwind mencocokkan ancestor ber-class group mana pun yang terdekat. Kalau wrapper reveal ikut memakai group anonim, hover di area kosong wrapper akan menyalakan spotlight kartu di dalamnya — dua sistem yang tidak berhubungan saling memicu karena berbagi nama.