An indicator only once you aim.
This page shows how a dragged panel finds its drop target. The drop indicator appears only once you are aiming, never flashes while you fly over targets or sit on a border, and the drop lands exactly where it said. Drag the Outline panel yourself, or pick a recorded path and compare the counts in this model and in a naive one that highlights whatever is under the pointer.
Built with @n-uf/motion (opens in a new tab) createSettle, createFrameLoopRedraws the rules of @n-uf/motion/drag (opens in a new tab) the drag intent and the three previews
Drag the Outline panel yourself, or pick a recorded path on the left.
Changes count the times the indicator appeared or moved; flashes count indicators shown for under 150 ms. Orange marks where the drop will land; the dashed box is Outline's own place.
Try drag intent on the desk constructions in the lab, with scripted flies, wobbles and flicks across the targets.
Pressed, lifted, flying, aiming, settling
A press moves nothing until the pointer passes 5 px; then the panel lifts and follows the pointer 1:1. While it flies there is no indicator. Slowing down or dwelling is aiming: the indicator shows, then the other panels make room. A release settles the panel into place; a release while flying lands on the target under the pointer, or returns home if there is none; Escape returns home.
Thresholds, holds and what a release does
When does the drag start?
After the pointer moves past a threshold. A click below it does nothing. On touch: a 180 ms hold, then the threshold.
mouse 5 px; touch 180 ms + 8 px
What does the dragged panel look like?
The real panel, raised, following the pointer 1:1 from the point you grabbed. It never stretches; it changes size only when it lands.
When does the indicator appear?
Only when all three hold: the pointer has left the panel's own place (home), there is a target under it, and you are aiming: moving slower than the aim speed, or holding nearly still for a moment. Time spent crossing a big target doesn't count.
aim speed 0.45 px/ms (450 px/s); still = within 12 px for 90 ms
How is speed measured?
Distance over the last few frames, smoothed, so one slow frame doesn't count as aiming.
window 50 ms
Which target wins when several overlap?
The most precise one: a rail icon or a tab strip beats a slot between sections, which beats "float here". Equal kinds: the smaller zone.
priority 1 · 2 · 3
What stops flicker on borders?
The current target holds until the pointer is clearly in another one (a band past the border), or is clearly out of all of them for a moment. A more precise target takes over at once, once the pointer is 4 px inside it.
band 10 px; gap grace 120 ms; breakthrough 4 px
When does a shown indicator move or go?
Leaving its target while flying hides it at once. While aiming, it moves to a new target only once that target has held for a moment, so moving between targets never flashes. A more precise target still takes over at once, 4 px inside it.
150 ms
When do the other panels make room?
Only after the indicator has stayed on the same target for a while, so nothing reflows while you pass over it. Leaving the target closes the room at once.
160 ms
What does a release do?
Lands where the indicator shows. If no indicator was showing yet (a quick flick), it lands on the target under the pointer at release; with none, the panel glides home. A release always lands on what is shown, so the drop follows the indicator, not the exact release point.
How do you cancel?
Escape, a release back over home, or the browser cancelling the pointer: the panel glides home and nothing else changes.
How do things move?
The indicator fades in and glides between targets; the landing and the room-making use settle (no overshoot, no jumps).
fade 90 ms; glide 110 ms; settle tempo 25
Long desks (the scrolling strip)
Near the stage's edge the strip scrolls, faster the closer the pointer is; the indicator is hidden while it scrolls.
edge 40 px
Keyboard
The same targets in a fixed order: a key to pick up, arrows to step through targets (the indicator shows each), Enter to drop, Escape to cancel.
Four properties, every path
- Flying over targets at speed shows no indicator (scenario: fly over, then aim).
- Holding on a border changes the indicator at most once (scenario: hold on a border).
- Moving between targets never flashes: a new target must hold 150 ms before the indicator moves to it (the flash count stays zero in every recorded path).
- The drop always equals the last indicator shown, or the target under the pointer when none was shown.
These rules are @n-uf/motion/drag's; the page runs them in its own code, and the cancel, glide, edge-scroll and keyboard details above are the demo's own behaviour around the model. Toolbar groups drop by the same model: see how a group finds its place. The landing glide is the settle curve.
Questions
How do I stop a drop indicator flickering between targets?
Feed the pointer to createDragIntent from @n-uf/motion/drag and draw what it says. It shows an indicator only once the pointer has left the item's home, a target is under it, and the pointer aims, meaning it slows down or holds still. Crossing targets at speed shows nothing.
What do hold and hysteresis do in drag intent?
Hysteresis keeps the current target: it holds within a band around its zone, 10 px by default, unless a more precise target is clearly under the pointer. The hold applies to switching: an aiming pointer moves the indicator only once the new target has held 150 ms. The other panels make room after 160 ms on one target.
Does drag intent fix the HTML5 dragenter and dragleave problem?
No. Drag intent is not a fix for the browser's HTML5 dragenter and dragleave events. It decides from the pointer's path when a drop indicator shows, which target it shows, when the others make room and what a release lands on. It is pure: the host feeds it the pointer once a frame and plain-data targets.
Where does a dragged item land when I release it?
On the shown target whenever one shows: the drop is what was shown. With no indicator showing, a release lands on the target under the pointer, and otherwise on nothing. release(point, targets) returns that target, or null for home, and a release over home does nothing.