Shots in this scene
Drag, Throw and Inertia
In one line
Pointer events follow a finger or a mouse, pointer capture keeps the drag alive, and the speed of the last 100 ms of movement is handed to a spring when the finger lets go.
Think of it like this
Pushing a shopping trolley. While your hand is on it, it goes where you push. When you let go it keeps rolling at the speed your hand gave it and slows down. Nobody expects it to stop the moment you let go, or to jump to the nearest bay.
Words you'll meet
New words in this topic, in plain English. Come back here whenever one feels fuzzy.
- pointer events
- One family of events (pointerdown, pointermove, pointerup) for mouse, touch and pen.
- pointer capture
- Telling the browser to send all later events of one pointer to your element.
- touch-action
- A CSS property saying which touch gestures the browser may handle and which are yours.
- rubber banding
- Resistance that grows as you drag past an edge.
Step by step
01Throw the card
Drag the card a short way and let go slowly: it returns. Flick it: it carries on to the far end. The message line prints the release speed.
02Use the keyboard
Tab to the card (blue outline), then ArrowRight and ArrowLeft. aria-valuetext says "open" or "closed".
03Read the rule table
The console of "Gesture rules": five movements classified, the rubber band values, three speeds and the pointer log.
04Hold, then release
Drag to the middle, hold for a second, let go: the message reads released at speed 0 px/s.
Try it yourself
- 1
Switch the momentum off
In "Throw the card" change
x + v * 0.15tox + v * 0indecide. A 40 px flick at 1250 px/s used to carry the card to the far end; now it falls back to 0, because only the position (40, short of 90) counts.
Code & diagrams
A card with two resting places. Release hands its speed to the spring of Topic 9.2.
Nothing is moving yet. Hover, click or scroll the page: every animation it starts shows up here as a track.
Console
Nothing logged yet.Expected console output
touch-action: pan-y
keyboard: ArrowRight and ArrowLeft do what a throw doesThe same rules the drawer uses, run on scripted values so the numbers are exact.
Nothing is moving yet. Hover, click or scroll the page: every animation it starts shows up here as a track.
Console
Nothing logged yet.Expected console output
moved (0, 0) → tap or scroll
moved (4, 2) → tap or scroll
moved (12, 3) → drag
moved (12, 40) → tap or scroll
moved (-30, 0) → drag
pulled past the edge by 10, 50, 100, 300, 1000 px → shown 8.5, 26.4, 35.9, 47.2, 53.0 px
flick: 1000 px/s
same drag, finger rested 200 ms: 0 px/s
one sample only: 0 px/s
pointer 1 grabs; pointer 2 ignored; cancel: settle with speed 0Break it on purpose
Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.
Break #1
Use a stale speed
In "Throw the card" replace performance.now() - b.t <= 80 in the moving line with true.
Myth vs fact
Myth
touch-action: none on everything is the safe choice.
Fact
It stops the page scrolling and zooming from that element. pan-y keeps vertical scrolling and still gives you the sideways drag.
Pro corner
Extra depth for experienced readers. New to this? Skip it for now and come back later.
- ▸
Our samples use
performance.now()when the handler runs.event.timeStampis the hardware's time and is more exact on a busy thread, but the course's clock cannot drive it.getCoalescedEvents()returns the moves a browser batched into one frame.
Remember this
- 1
The problem this solves: following a finger is easy. The hard part is the release. If the drawer snaps to the nearest edge, a flick feels ignored. If it ignores that you stopped before letting go, it flies off at a stale speed. And it must work with mouse, touch, pen and keyboard.
- 2
Pointer events and capture.
pointerdown,pointermoveandpointerupcover mouse, touch and pen, each pointer with anid.setPointerCapture(id)sends that pointer's later events to your element, even when the finger leaves it or the window. It can throw if the pointer is gone, so wrap it intry. - 3
**
touch-actiondecides who owns a swipe.** By default a moving touch belongs to the browser: it scrolls and your element getspointercancel.touch-action: pan-yleaves vertical scrolling to the browser and gives you sideways movement.nonegives you everything: use it only where needed. - 4
A tap is not a drag. A finger wobbles. Wait for 6 px of mostly sideways movement: (4, 2) is still a tap, (12, 3) is a drag, (12, 40) is a vertical scroll. After a real drag, swallow the
clickthat follows, so it cannot press a button under the finger. - 5
Speed from the last 100 ms. Keep recent positions with their times. The speed is distance over time between the first and last sample within 100 ms. If the last sample is older than about 80 ms the finger rested: speed 0. In the second example a flick gives 1000 px/s, and the same drag after a 200 ms rest gives 0.
- 6
Where a throw ends. Add 0.15 s of the release speed to the position and see which side of the middle it lands on. In the first example a 40 px flick at 1250 px/s opens (40 + 188 is past the middle); a slow 60 px drag held for 200 ms falls back. Then start a spring there with that speed.
- 7
Edges. Rubber banding resists past the edge:
56 × (1 - 1 ÷ (past ÷ 56 + 1))gives 8.5 px for a 10 px pull, 35.9 for 100, 53.0 for 1000. Follow one pointer and ignore others. Onpointercancelsettle with speed 0. Every drag needs a keyboard twin: the card hasrole="slider"and moves on the arrow keys like a throw.
Explain it without notes
Why does the drawer capture the pointer, and what does touch-action change?
How is the release speed computed, and why check the age of the last sample?
Practice
Make the card rubber-band when it is dragged past either end.
Add Home and End keys to the card.
Done when you can
You can capture a pointer and say what touch-action does
You can tell a tap from a drag
You can compute a release speed and say when it is zero