Command Palette

Search for a command to run...

Hectal
Course outline
Topic 8.2Intermediate ~22 min · step 2 of 6

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.

0/6 · 0%

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. 1

    Make the threshold too big

    In the click bench set CLICK_DISTANCE = 40. The line slow drag (30 px) now says CLICK and the total becomes 4. A 30 pixel drag moved the bike and also chose a part. Too small, and a shaky finger never clicks.

  2. 2

    Let touch hover

    In the hover example delete the line if (e.pointerType === "touch") return; from the pointermove handler. The line touch move over the bell now says hovered: 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

Click or drag: a test bench for the rule New tab

The bench sends real PointerEvents to the canvas, then prints what the handler decided.

idle

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: 3
Hover once per frame, and touch has no hover New tab

Move the mouse over the three balls: the one under it glows and the cursor becomes a hand. The text output comes from simulated events.

idle

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: bell

Break 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".

terminal
$ Run
── what you'll see ──
press, then the browser cancels: CLICK
The total at the end becomes 4 instead of 3.

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 a click after a tap; pointer-event code can ignore them. Do not call preventDefault() on pointerdown unless you know why: it changes which of those later events are sent.

Remember this

  1. 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. 2

    One set of events for every input. Pointer events replace both: pointerdown, pointermove, pointerup, pointercancel, pointerenter and pointerleave. Each event has pointerType ("mouse", "touch" or "pen"), pointerId (to tell two fingers apart) and button (0 is the main button, 2 the right one). One handler serves all three inputs.

  3. 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. 4

    Cancelled gestures. If the browser takes a touch for its own use (a scroll, a pinch-zoom, an incoming call), you get pointercancel and no pointerup. Forget the press there too, or the next release looks like a click. To keep the browser out of canvas touches, set touch-action: none on the canvas; to let a page still scroll up and down, use touch-action: pan-y.

  5. 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

01

Why is a click detected by distance and not by time between press and release?

02

A phone user taps a thin part and nothing happens. Name two causes and fixes.

Practice

01

Write isClick(down, up, limit = 8) that takes two { x, y } points and returns whether they are within limit pixels.

02

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 pointercancel

  • Highlight on hover once per frame, and not for touch

  • Set the cursor from what the ray hits