Snap Scroller & Deteksi Sentuh Aman
Pengendali navigasi adegan terpusat yang menyatukan input roda mouse, sapuan jari (swipe), dan papan ketik dengan perlindungan terhadap elemen klik.
Membaca sekitar 2 menit
Pengendali navigasi adegan terpusat yang menyatukan input roda mouse, sapuan jari (swipe), dan papan ketik dengan perlindungan terhadap elemen klik.
SnapScroller mengunci jeda antar-guliran dengan cooldown ref dan memeriksa elemen di titik sentuh awal memakai document.elementFromPoint agar tombol di dalam overlay tidak salah terbaca sebagai swipe.
Mengatasi masalah klasik di mana lapisan overlay transparan menghalangi scroll atau sebaliknya membuat tombol gagal diklik di layar ponsel.
Masalahnya dulu
Karena seluruh adegan ditumpuk dengan position: fixed di atas satu layar penuh, halaman ini secara teknis tidak memiliki tinggi guliran alami. Jika kita mengandalkan scroll bawaan browser, trackpad laptop yang sensitif bisa melompati tiga adegan sekaligus dalam satu usapan, sementara di layar sentuh ponsel kita harus membedakan mana usapan pindah adegan dan mana ketukan jari pada tombol 'Buka Google Maps' atau 'Lihat Galeri Foto'.
Cara ngodingan ini menyelesaikannya
Komponen SnapScroller mengambil alih penuh kendali navigasi dengan memasang pendengar event pasif (passive listeners) untuk wheel, touchstart, touchmove, touchend, dan keydown langsung di objek window. Setiap perpindahan adegan dikunci menggunakan coolRef selama 750ms agar transisi kamera punya waktu bernapas dan tidak bisa di-spam oleh trackpad berkecepatan tinggi.
Lapisan overlay harus tembus pandang terhadap guliran jari, namun tombol di dalamnya tetap wajib mengenali sentuhan tanpa memicu perpindahan adegan.
Implementasi di ngodingan ini
Trik paling krusial ada pada penanganan layar sentuh: saat jari menyentuh layar (touchstart), kode memanggil document.elementFromPoint untuk mengintip elemen terdalam yang memiliki pointer-events aktif. Jika jari mendarat di atas tautan atau tombol, penanda startedOnInteractive akan membatalkan aksi swipe:
const INTERACTIVE = 'a, button, input, select, textarea, [role="button"]';
const onTS = (e: TouchEvent) => {
const t = e.touches[0];
ty0 = t.clientY;
tx0 = t.clientX;
isSwipe = false;
const el = document.elementFromPoint(t.clientX, t.clientY) as HTMLElement | null;
startedOnInteractive = !!el?.closest(INTERACTIVE);
};
const onTM = (e: TouchEvent) => {
if (!e.touches[0]) return;
const dy = Math.abs(ty0 - e.touches[0].clientY);
const dx = Math.abs(tx0 - e.touches[0].clientX);
if (dy > 70 && dy > dx * 2) isSwipe = true;
};
const onTE = (e: TouchEvent) => {
if (coolRef.current || !isSwipe || startedOnInteractive) return;
const dy = ty0 - e.changedTouches[0].clientY;
handleScroll(dy > 0 ? 1 : -1);
};Jebakan dan detail yang harus dijaga
- Seluruh komponen pembungkus adegan (Ov) diberi pointer-events: none secara default agar tidak memblokir sapuan layar, dan hanya diaktifkan menjadi auto ketika adegan berisi tombol sedang tampil (show === true).
- Syarat dy > 70 && dy > dx * 2 memastikan hanya sapuan vertikal yang tegas yang dihitung sebagai navigasi adegan, sehingga gerakan jari miring yang tidak sengaja tidak langsung memindahkan halaman.