:on(drag)
rulesince 0.9.0experimentalrequires runtimePointer drag gestures as event rules: :on(drag) runs on every move, drag(start), drag(end) and drag(cancel) at the edges. event(x) and event(y) give positions that continue across drags.
On this page · 10 sections+−
Syntax
<selector>:on(drag | drag(start | move | end | cancel) [, prevent()]) { --<state>: set(event(x));}:on(drag)Rule · experimental · hover any red tokenPointer drag gestures as event rules: :on(drag) runs on every move, drag(start), drag(end) and drag(cancel) at the edges. event(x) and event(y) give positions that continue across drags.Reference →
registry syntax: :on(drag) | :on(drag(start | move | end | cancel))
Parameters
- phase
start,move,endorcanceloptional :on(drag)is:on(drag(move)).- prevent()modifieroptional
- Cancels the default of the
pointerdownthat starts the gesture.stop()is a compile error with drag.
Effect
No value. Each phase runs the rule’s mutations, as any :on() rule does.
Example
.card { --x: state(0px); --y: state(0px); --dragging: state(false); translate: var(--x) var(--y); touch-action: none;}.card:on(drag(start)) { --dragging: set(true);}/* event(x): the state at drag start plus the distance moved. */.card:on(drag) { --x: set(clamp(-110px, event(x), 110px)); --y: set(clamp(-55px, event(y), 55px));}.card:on(drag(end)) { --dragging: set(false);}.card:when(--dragging) { cursor: grabbing; scale: 1.05;}/* The same state from the keyboard. */.card:on(keydown(ArrowRight), prevent()) { --x: set(min(var(--x) + 10px, 110px)); }.card:on(keydown(ArrowLeft), prevent()) { --x: set(max(var(--x) - 10px, -110px)); }.card:on(keydown(ArrowDown), prevent()) { --y: set(min(var(--y) + 10px, 55px)); }.card:on(keydown(ArrowUp), prevent()) { --y: set(max(var(--y) - 10px, -55px)); }state()Function · hover any red tokenMakes each matching element the owner of an independent state with an initial value.Reference →
Resultfocusable card, arrow keys
waiting for interaction
Correct and incorrect usage
correctA slider thumb clamped to its track
slider.cssreactive
.slider { --v: state(0px); }.slider .thumb { translate: var(--v) 0; touch-action: none;}.slider .thumb:on(drag) { --v: set(clamp(0px, event(x), 200px)); }.slider .thumb:on(keydown(ArrowRight), prevent()) { --v: set(min(var(--v) + 10px, 200px)); }.slider .thumb:on(keydown(ArrowLeft), prevent()) { --v: set(max(var(--v) - 10px, 0px)); }The state lives on the track. event(x) starts from the current --v at each drag, and the arrow keys change the same state. The thumb needs tabindex="0" in the HTML to receive keys.
incorrectstop() in a drag rule
card.cssreactive
.card { --x: state(0px); }.card:on(drag, stop()) { --x: set(event(x)); }A compile error: a drag is not a bubbling DOM event, so there is nothing to stop.
Behavior
- Experimental: no real touch or pen hardware has been tested yet.
- Arming. A press with the primary button arms a gesture on the innermost element on the path that a drag rule matches, unless it is
:disabled, inside[inert]or already being dragged. The gesture starts after 3 CSS px of movement. Below that, the press and the click stay native and no phase runs. - Phases.
startruns once, thenmove(:on(drag)) on each pointer move. Moves run synchronously; state writes are coalesced into one style write per frame, and the last position is never dropped.pointeruprunsend.pointercancelor a lost pointer capture runscancel, thenend, so--dragging: set(false)indrag(end)always runs. - Positions continue.
event(x)is the value of the mutated state when the drag started, plus the pointer distance on that axis..card:on(drag) { --x: set(event(x)); }therefore moves the card on from where it was, over any number of drags, and never jumps back to the pointer. For raw displacement useevent(delta-x);event(movement-x)is the distance since the previous move andevent(start-x)the press position in viewport coordinates. Seeevent(). - Pointer capture. The element captures the pointer, so moves keep arriving when the pointer leaves it. Each gesture follows its own pointer: concurrent pointers drag different elements independently, and a second pointer on an element that is already being dragged is ignored.
- Fields inside. A press on an
input,textarea,selector editable element inside the draggable never starts a drag, so selecting text and moving sliders keep working. Buttons and links inside can start one. - The click after a drag does not run
:on(click)rules on that element or its ancestors. The native click is not changed. - DOM changes. Moving the element to another parent during a drag makes the browser release the capture, so the gesture is cancelled:
cancel, thenend. Removing the element, or a class change that makes it stop matching, ends the gesture without running any phase; a state set indrag(start)keeps its value. - Cleanup. One capturing
pointerdownlistener exists while drag rules exist. Move listeners exist only during a gesture, and the capture is released at its end.ReactiveCSS.stop()ends every gesture.
Supported contexts
- Give the draggable element
touch-action: none, or touch scrolling takes over the gesture. - The same placement rules as
:on(). A custom bundle needs thegesturesfeature.
Limitations
- Accessibility. A drag has no keyboard equivalent by itself. Change the same state from the keyboard as well, as the example does with
:on(keydown(ArrowRight), prevent())rules, or offer a native range input. - Not HTML drag and drop: no
dragstartordrop, no sortable lists and no drop targets. - There is no
drag(x)value function;event(x)covers it. - Touch and pen are covered by synthetic events only. Real devices are on the manual test list.
Examples
- Draggable card · API examples, runs the real runtime
Related
Experimental · syntax may change between minor versions