useMove
Handles move interactions across mouse, touch, and keyboard, including dragging with the mouse or touch, and using the arrow keys. Normalizes behavior across browsers and platforms, and ignores emulated mouse events on touch devices.
install | yarn add @react-aria/interactions |
---|---|
version | 3.8.4 |
usage | import {useMove} from '@react-aria/interactions' |
API#
useMove(
(props: MoveEvents
)): MoveResult
Features#
useMove
handles move interactions across mouse, touch, and keyboard. A move interaction starts when a
user presses down with a mouse or their finger on the target, and ends when they lift their pointer. Move
events are fired as the pointer moves around, and specify the distance that the pointer traveled since the last
event. In addition, move events are fired when the user focuses the target element and uses the arrow keys.
- Handles mouse and touch events
- Handles arrow key presses
- Uses pointer events where available, with fallbacks to mouse and touch events
- Ignores emulated mouse events in mobile browsers
- Handles disabling text selection on mobile while the press interaction is active
- Normalizes many cross browser inconsistencies
Usage#
useMove
returns props that you should spread onto the target element:
Name | Type | Description |
moveProps | HTMLAttributes<HTMLElement> | Props to spread on the target element. |
useMove
supports the following event handlers:
Name | Type | Description |
onMoveStart | (
(e: MoveStartEvent
)) => void | Handler that is called when a move interaction starts. |
onMove | (
(e: MoveMoveEvent
)) => void | Handler that is called when the element is moved. |
onMoveEnd | (
(e: MoveEndEvent
)) => void | Handler that is called when a move interaction ends. |
Each of these handlers is fired with a MoveEvent
, which exposes information about the target and the
type of event that triggered the interaction.
Name | Type | Description |
type | 'move' | The type of move event being fired. |
deltaX | number | The amount moved in the X direction since the last event. |
deltaY | number | The amount moved in the Y direction since the last event. |
pointerType | PointerType | The pointer type that triggered the move event. |
shiftKey | boolean | Whether the shift keyboard modifier was held during the move event. |
ctrlKey | boolean | Whether the ctrl keyboard modifier was held during the move event. |
metaKey | boolean | Whether the meta keyboard modifier was held during the move event. |
altKey | boolean | Whether the alt keyboard modifier was held during the move event. |
Example#
This example shows a ball that can be moved by dragging with a mouse or touch, or by tabbing to it and using the arrow keys on your keyboard. The movement is clamped so that the ball cannot be dragged outside a box. In addition, all of the move events are logged below so that you can inspect what is going on.
import {useMove} from '@react-aria/interactions';
function Example() {
const CONTAINER_SIZE = 200;
const BALL_SIZE = 30;
let [events, setEvents] = React.useState([]);
let [color, setColor] = React.useState('black');
let [position, setPosition] = React.useState({
x: 0,
y: 0
});
let clamp = (pos) => Math.min(Math.max(pos, 0), CONTAINER_SIZE - BALL_SIZE);
let { moveProps } = useMove({
onMoveStart(e) {
setColor('red');
setEvents(
(events) => [
`move start with pointerType = `,
...events
]
);
},
onMove(e) {
setPosition(({ x, y }) => {
// Normally, we want to allow the user to continue
// dragging outside the box such that they need to
// drag back over the ball again before it moves.
// This is handled below by clamping during render.
// If using the keyboard, however, we need to clamp
// here so that dragging outside the container and
// then using the arrow keys works as expected.
if (e.pointerType === 'keyboard') {
x = clamp(x);
y = clamp(y);
}
x += e.deltaX;
y += e.deltaY;
return { x, y };
});
setEvents(
(events) => [
`move with pointerType = , deltaX = , deltaY = `,
...events
]
);
},
onMoveEnd(e) {
setColor('black');
setEvents(
(events) => [`move end with pointerType = `, ...events]
);
}
});
return (
<>
<div
style={{
width: CONTAINER_SIZE,
height: CONTAINER_SIZE,
background: 'white',
border: '1px solid black',
position: 'relative',
touchAction: 'none'
}}
>
<div
{...moveProps}
tabIndex={0}
style={{
width: BALL_SIZE,
height: BALL_SIZE,
borderRadius: '100%',
position: 'absolute',
left: clamp(position.x),
top: clamp(position.y),
background: color
}}
/>
</div>
<ul
style={{
maxHeight: '200px',
overflow: 'auto'
}}
>
{events.map((e, i) => <li key={i}>{e}</li>)}
</ul>
</>
);
}
import {useMove} from '@react-aria/interactions';
function Example() {
const CONTAINER_SIZE = 200;
const BALL_SIZE = 30;
let [events, setEvents] = React.useState([]);
let [color, setColor] = React.useState('black');
let [position, setPosition] = React.useState({
x: 0,
y: 0
});
let clamp = (pos) =>
Math.min(Math.max(pos, 0), CONTAINER_SIZE - BALL_SIZE);
let { moveProps } = useMove({
onMoveStart(e) {
setColor('red');
setEvents(
(events) => [
`move start with pointerType = `,
...events
]
);
},
onMove(e) {
setPosition(({ x, y }) => {
// Normally, we want to allow the user to continue
// dragging outside the box such that they need to
// drag back over the ball again before it moves.
// This is handled below by clamping during render.
// If using the keyboard, however, we need to clamp
// here so that dragging outside the container and
// then using the arrow keys works as expected.
if (e.pointerType === 'keyboard') {
x = clamp(x);
y = clamp(y);
}
x += e.deltaX;
y += e.deltaY;
return { x, y };
});
setEvents(
(events) => [
`move with pointerType = , deltaX = , deltaY = `,
...events
]
);
},
onMoveEnd(e) {
setColor('black');
setEvents(
(events) => [
`move end with pointerType = `,
...events
]
);
}
});
return (
<>
<div
style={{
width: CONTAINER_SIZE,
height: CONTAINER_SIZE,
background: 'white',
border: '1px solid black',
position: 'relative',
touchAction: 'none'
}}
>
<div
{...moveProps}
tabIndex={0}
style={{
width: BALL_SIZE,
height: BALL_SIZE,
borderRadius: '100%',
position: 'absolute',
left: clamp(position.x),
top: clamp(position.y),
background: color
}}
/>
</div>
<ul
style={{
maxHeight: '200px',
overflow: 'auto'
}}
>
{events.map((e, i) => <li key={i}>{e}</li>)}
</ul>
</>
);
}
import {useMove} from '@react-aria/interactions';
function Example() {
const CONTAINER_SIZE =
200;
const BALL_SIZE = 30;
let [
events,
setEvents
] = React.useState([]);
let [color, setColor] =
React.useState(
'black'
);
let [
position,
setPosition
] = React.useState({
x: 0,
y: 0
});
let clamp = (pos) =>
Math.min(
Math.max(pos, 0),
CONTAINER_SIZE -
BALL_SIZE
);
let { moveProps } =
useMove({
onMoveStart(e) {
setColor('red');
setEvents(
(events) => [
`move start with pointerType = `,
...events
]
);
},
onMove(e) {
setPosition(
({ x, y }) => {
// Normally, we want to allow the user to continue
// dragging outside the box such that they need to
// drag back over the ball again before it moves.
// This is handled below by clamping during render.
// If using the keyboard, however, we need to clamp
// here so that dragging outside the container and
// then using the arrow keys works as expected.
if (
e.pointerType ===
'keyboard'
) {
x = clamp(
x
);
y = clamp(
y
);
}
x +=
e.deltaX;
y +=
e.deltaY;
return {
x,
y
};
}
);
setEvents(
(events) => [
`move with pointerType = , deltaX = , deltaY = `,
...events
]
);
},
onMoveEnd(e) {
setColor(
'black'
);
setEvents(
(events) => [
`move end with pointerType = `,
...events
]
);
}
});
return (
<>
<div
style={{
width:
CONTAINER_SIZE,
height:
CONTAINER_SIZE,
background:
'white',
border:
'1px solid black',
position:
'relative',
touchAction:
'none'
}}
>
<div
{...moveProps}
tabIndex={0}
style={{
width:
BALL_SIZE,
height:
BALL_SIZE,
borderRadius:
'100%',
position:
'absolute',
left: clamp(
position.x
),
top: clamp(
position.y
),
background:
color
}}
/>
</div>
<ul
style={{
maxHeight:
'200px',
overflow:
'auto'
}}
>
{events.map((
e,
i
) => (
<li key={i}>
{e}
</li>
))}
</ul>
</>
);
}