Course outline
Pointer Events: Hover, Click and Drag
In one line
Pointer events give one set of events for a mouse, a finger and a pen. You use them to highlight a part on hover (once per frame), to tell a click from a drag by distance, to change the cursor, and to treat touch honestly: a finger has no hover.
Think of it like this
A doorbell and a door handle. Pressing the bell and letting go quickly is a ring. Pressing the handle and pulling is a different action, even though both start with a press. The difference is what the hand does between press and release. A click and a drag are the same: one press, then either nothing much or a lot of movement.
Words you'll meet
New words in this topic, in plain English. Come back here whenever one feels fuzzy.
- pointer event
- A browser event for a mouse, a finger or a pen, with the same names and fields for all three.
- drag threshold
- The distance, in pixels, a pointer may move after pressing and still count as a click.
- pointer capture
canvas.setPointerCapture(id)makes the canvas receive every move and release of that pointer, even after it leaves the canvas.- touch-action
- A CSS property that tells the browser which touch gestures it may handle itself (scroll, zoom) and which it must leave to your code.
Step by step
01Listen for the pointer, not the mouse
Use pointerdown on the canvas. Use pointermove and pointerup on window (or capture the pointer), so a drag that leaves the canvas still ends properly.
02Decide click or drag by distance
On every move, Math.hypot(e.clientX - press.x, e.clientY - press.y). Past the limit, set press = null. On pointerup, a surviving press is a click. Check pointerId so a second finger does not confuse the first.
03Cancel on pointercancel
pointercancel means the browser took the gesture. Set press = null. Without it the next lift can look like a click on the wrong thing.
04Hover only for pointers that can hover
Ignore pointermove when pointerType === "touch". Pens and mice hover. On pointerleave clear the hover, or a part stays lit after the mouse has left the page.
Try it yourself
- 1
Make the threshold too big
In the click bench set
CLICK_DISTANCE = 40. The lineslow drag (30 px)now saysCLICKand the total becomes4. A 30 pixel drag moved the bike and also chose a part. Too small, and a shaky finger never clicks. - 2
Let touch hover
In the hover example delete the line
if (e.pointerType === "touch") return;from thepointermovehandler. The linetouch move over the bellnow sayshovered: bell. Touch moves only arrive while a finger is down, so this "hover" is really "the part under a dragging finger". It lights parts during every drag, which is not what hover means.
Code & diagrams
The bench sends real PointerEvents to the canvas, then prints what the handler decided.
Console
Run the scene to see its console.Expected console output
tap, no movement CLICK
shaky tap (3 px) CLICK
slow drag (30 px) not a click
fast flick (one 90 px jump) not a click
drag and come back to the start not a click
finger tap (touch) CLICK
right mouse button not a click
press, then the browser cancels: not a click
total clicks: 3Move the mouse over the three balls: the one under it glows and the cursor becomes a hand. The text output comes from simulated events.
Console
Run the scene to see its console.Expected console output
50 moves over the saddle -> hovered: saddle | raycasts: 1 | cursor: pointer
mouse leaves -> hovered: nothing | cursor: default
touch move over the bell -> hovered: nothing (touch has no hover)
touch tap on the bell -> highlighted: bellBreak it on purpose
Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.
Break #1
Forget pointercancel
In the click bench delete the line that starts with window.addEventListener("pointercancel".
Myth vs fact
Myth
A click event is enough: the browser already tells you it was a click.
Fact
The browser fires click after press and release on the same element, even when the pointer moved across the whole canvas in between. Dragging the bike would then also choose a part. Measure the distance yourself.
Pro corner
Extra depth for experienced readers. New to this? Skip it for now and come back later.
- ▸
Browsers also send compatibility
mouse*events and aclickafter a tap; pointer-event code can ignore them. Do not callpreventDefault()onpointerdownunless you know why: it changes which of those later events are sent.
Remember this
- 1
The problem this solves: the showroom has two jobs for one gesture. A press-and-drag on the canvas turns the bike (Phase 7). A press-and-release in one place chooses a part. If you pick on every
pointerdown, every drag also selects something. If you do not tell them apart, one of them feels broken. - 2
One set of events for every input. Pointer events replace both:
pointerdown,pointermove,pointerup,pointercancel,pointerenterandpointerleave. Each event haspointerType("mouse","touch"or"pen"),pointerId(to tell two fingers apart) andbutton(0 is the main button, 2 the right one). One handler serves all three inputs. - 3
Click or drag? Remember where the pointer went down. On every move, measure the distance from that point. Past a small limit (about 5 to 10 pixels; fingers wobble more than a mouse) it is a drag, so forget the press. On release, if the press is still remembered, it is a click.
- 4
Cancelled gestures. If the browser takes a touch for its own use (a scroll, a pinch-zoom, an incoming call), you get
pointercanceland nopointerup. Forget the press there too, or the next release looks like a click. To keep the browser out of canvas touches, settouch-action: noneon the canvas; to let a page still scroll up and down, usetouch-action: pan-y. - 5
Hover. A mouse and some pens can hover: the pointer moves with no button down. A finger cannot: it exists only while it touches the screen. So a tap must do the job that hover does for a mouse, or the information is out of reach on a phone.
Explain it without notes
Why is a click detected by distance and not by time between press and release?
A phone user taps a thin part and nothing happens. Name two causes and fixes.
Practice
Write isClick(down, up, limit = 8) that takes two { x, y } points and returns whether they are within limit pixels.
Write a pointerleave and pointercancel pair that makes sure no part stays highlighted and no press stays remembered.
Done when you can
Use pointer events for mouse, touch and pen with one handler
Tell a click from a drag by distance, and clear the press on
pointercancelHighlight on hover once per frame, and not for touch
Set the cursor from what the ray hits