Fitur andalan toko Budi untuk trik baru musim ini adalah papan sketsa: pengunjung bisa menggambar coretan langsung di atas gambar produk sebelum memesan cetak kustom. Tantangannya, alat gambar itu harus bekerja dengan tiga jenis perangkat sekaligus — mouse di komputer, jari di ponsel, dan stylus di tablet. Cara lama menangani ini berarti menulis dua keluarga peristiwa terpisah (mousedown dan kawan-kawan untuk mouse, touchstart dan kawan-kawan untuk sentuhan), lalu menjaga keduanya tetap selaras setiap ada perubahan.
Pointer Events menggabungkan semuanya ke dalam satu keluarga peristiwa. Browser menyebut semua alat penunjuk itu dengan satu istilah: pointer — entitas abstrak yang bisa ditekan, digerakkan, dan dilepas, apa pun bentuk fisiknya. Chapter ini membahas pasangan pointerdown, pointermove, dan pointerup, properti pointerType untuk membedakan perangkat, serta sekilas setPointerCapture yang menjaga perjalanan pointer tetap terkendali.
Satu Keluarga Peristiwa untuk Semua Pointer #
Aturannya mudah diingat karena meniru peristiwa mouse yang sudah akrab: pointerdown saat pointer ditekan, pointermove saat pointer bergerak, pointerup saat pointer dilepas. Perbedaannya, tiga peristiwa ini menyala untuk mouse, sentuhan, dan stylus — satu pendaftaran berlaku untuk semuanya. Detail perangkat asal tersedia pada object event:
| Properti | Isi |
|---|---|
pointerType | "mouse", "touch", atau "pen" |
pointerId | nomor unik pointer; setiap jari pada sentuhan multitouch punya id sendiri |
isPrimary | true untuk pointer utama dalam satu sesi |
clientX, clientY | posisi pointer terhadap area pandang, dalam piksel |
Dengan pointerId, fitur seperti menggambar dua jari sekaligus bisa dibedakan: gerakan jari pertama dan jari kedua membawa id berbeda, sehingga coretan keduanya tidak tertukar.
Perbandingan dengan peristiwa mouse yang lama memudahkan pemetaan:
| Peristiwa mouse | Padanan pointer |
|---|---|
mousedown | pointerdown |
mousemove | pointermove |
mouseup | pointerup |
mouseenter, mouseleave | pointerenter, pointerleave |
Nama-namanya selaras, tetapi padanannya mencakup semua jenis pointer — mouse, sentuhan, dan stylus — dalam satu pendaftaran.
Papan Gambar Pertama #
Berikut papan sketsa versi paling sederhana — satu kotak yang mencatat setiap tahap sentuhan:
<div id="papan">Papan Gambar</div>// pointer-dasar.js — pointerdown, pointermove, pointerup pada papan gambar
const papan = document.querySelector("#papan");
let sedangMenggambar = false;
papan.addEventListener("pointerdown", function (event) {
sedangMenggambar = true;
console.log(`mulai menggambar | pointerType: ${event.pointerType}`);
});
papan.addEventListener("pointermove", function () {
if (sedangMenggambar) {
console.log("menggambar...");
}
});
papan.addEventListener("pointerup", function (event) {
sedangMenggambar = false;
console.log(`selesai menggambar | pointerType: ${event.pointerType}`);
});Simulasi kita meniru sesi menggambar dengan mouse: pointer ditekan, digerakkan dua kali, lalu dilepas — kemudian satu ketukan pendek dengan stylus. Keluaran berikut dicatat dari eksekusi nyata berkas ini di lingkungan DOM jsdom, dengan urutan yang sama di console browser.
Output yang Dihasilkan (console browser):
mulai menggambar | pointerType: mouse
menggambar...
menggambar...
selesai menggambar | pointerType: mouse
mulai menggambar | pointerType: pen
selesai menggambar | pointerType: penMari kita telaah:
- Baris 3 (
let sedangMenggambar = false): state kecil ini memisahkan “pointer sedang bergerak” dari “pointer sedang menekan”. Tanpa pembeda,pointermoveakan terpicu oleh setiap gerakan kursor biasa — dan papan mencetak catatan meski tidak ada yang menggambar. - Baris 5–8 (
pointerdown): menekan papan menyalakan penandasedangMenggambardan mencatat jenis perangkatnya. Inilah momen memulai coretan baru. - Baris 10–14 (
pointermovedengan syarat): handler hanya mencetak bila penanda menyala. Dua barismenggambar...pada keluaran sesuai dengan dua kali gerakan dalam keadaan ditekan. - Baris 16–19 (
pointerup): melepas pointer memadamkan penanda dan mencatat selesainya sesi. Dua baris terakhir keluaran menunjukkan siklus yang sama terjadi untuk stylus — kode yang sama, tanpa cabang khusus perangkat.
Satu catatan agar tidak salah langkah: peristiwa pointer tidak menggantikan click. Setelah pointerdown dan pointerup selesai pada elemen yang sama, browser tetap mengirim peristiwa click — sehingga tombol biasa tetap ditangani dengan click, dan keluarga pointer dipakai ketika butuh detail gerakan: menggambar, menggeser, atau mengukur durasi tekan.
Membedakan Perangkat lewat pointerType #
Properti pointerType membuat penyesuaian per perangkat jadi satu baris if. Papan Budi, misalnya, bisa memilih ketebalan goresan berbeda: if (event.pointerType === "touch") memberi garis lebih tebal untuk jari, sementara "pen" mendapat ketelitian penuh. Pemeriksaan ini juga tempat yang tepat menonaktifkan fitur bagi perangkat tertentu — misalnya menolak sentuhan sambil menunggu dukungan multitouch selesai dibangun.
Posisi Pointer: clientX dan clientY #
Untuk papan gambar sungguhan, penanda state saja belum cukup — goresan butuh koordinat. Object event pointer membawa clientX dan clientY: posisi pointer terhadap area pandang halaman, dalam piksel. Handler pointermove membaca keduanya dan meneruskan posisi terbaru ke kode penggambar, sehingga setiap gerakan menghasilkan titik goresan yang mengikuti pointer.
Karena pointermove bisa menyala puluhan kali per detik, handler yang berat akan membuat goresan tersendat. Praktik yang lazim: handler hanya menyimpan posisi terbaru, lalu penggambaran diserahkan ke fungsi yang lebih hemat — teknik penghalusan seperti requestAnimationFrame dibahas pada section Performance.
Satu kebiasaan yang perlu dibentuk sejak awal: jangan anggap pointermove hanya datang dari mouse. Pada perangkat sentuh, gerakan jari memicu peristiwa yang sama, dan browser di ponsel juga memakai gerakan itu untuk menggulir halaman. Bila papan gambar harus menangani gerakan sendiri, penyerapan gerakan oleh browser perlu dimatikan dari sisi CSS — lewat properti touch-action: none pada elemen papan.
setPointerCapture: Singkat tapi Penting #
Secara bawaan, pointermove dan pointerup dikirim ke elemen yang ada di bawah pointer pada saat itu. Konsekuensinya menyulitkan menggambar: begitu goresan melewati tepi papan, peristiwa berpindah ke elemen lain dan coretan putus. Method setPointerCapture() menyelesaikannya dengan memaksa seluruh peristiwa pointer berikutnya tetap dikirim ke elemen yang menangkap, sampai pointer dilepas:
// dipanggil di dalam handler pointerdown
papan.setPointerCapture(event.pointerId);Setelah baris itu berjalan, goresan tetap terpantau meski pointer keluar dari batas papan — dan tangkapan dilepas otomatis saat pointerup. Method ini termasuk API yang hanya tersedia di browser, jadi perilakunya kita rujuk pada spesifikasi, bukan pada lingkungan pengujian terminal.
Tangkapan juga berpasangan rapi dengan pointercancel: bila browser mengambil alih interaksi — misalnya gerakan jari berubah menjadi pengguliran halaman — tangkapan dilepas dan peristiwa penutup dikirim, sehingga state papan bisa dipulihkan. Untuk kasus yang lebih khusus, releasePointerCapture() melepas tangkapan lebih awal tanpa menunggu pointerup.
Perangkat masukan terakhir yang sering menjadi sumber interaksi adalah form: kolom teks yang diketik, pilihan yang diganti, tombol kirim yang ditekan. Ketiga aksi itu punya peristiwanya sendiri — dan urutannya penting diketahui. Itulah bahasan chapter berikutnya.
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
pointer-dasar.js
Unduh berkas// pointer-dasar.js — pointerdown, pointermove, pointerup pada papan gambar
const papan = document.querySelector("#papan");
let sedangMenggambar = false;
papan.addEventListener("pointerdown", function (event) {
sedangMenggambar = true;
console.log(`mulai menggambar | pointerType: ${event.pointerType}`);
});
papan.addEventListener("pointermove", function () {
if (sedangMenggambar) {
console.log("menggambar...");
}
});
papan.addEventListener("pointerup", function (event) {
sedangMenggambar = false;
console.log(`selesai menggambar | pointerType: ${event.pointerType}`);
});