Gestures
A powerful gesture recognition system for the browser.
Motion extends the basic set of event listeners provided by React with a simple yet powerful set of UI gesture recognisers.
It currently has support for hover, tap, pan and drag gesture detection. Each gesture has a series of event listeners that you can attach to your motion
component.
#Animation helpers
motion
components provide multiple gesture animation props: whileHover
, whileTap
, whileFocus
, whileDrag
and whileInView
. These can define animation targets to temporarily animate to while a gesture is active.
<motion.button whileHover={{ scale: 1.2, transition: { duration: 1 }, }} whileTap={{ scale: 0.9 }}/>
All props can be set either as a target of values to animate to, or the name of any variants defined via the variants
prop. Variants will flow down through children as normal.
<motion.button whileTap="tap" whileHover="hover" variants={buttonVariants}> <svg> <motion.path variants={iconVariants} /> </svg></motion.button>
motion
components automatically manage the interplay between these while
props. So for instance, if hovering starts and stops while the tap gesture is active, the tap gesture receives priority and any properties defined on both will remain in their tapped state.
Likewise, if both gestures are defined and tapping ends, the component will know to animate either to the state defined in whileHover
, or the component's original state, depending on whether tapping ends inside or outside of the component.
#Propagation
Children can stop pointer events propagating to parent motion
components using the Capture
React props.
For instance, a child can stop drag and tap gestures and their related while
animations from firing on parents by passing e.stopPropagation()
to onPointerDownCapture
.
<motion.div whileTap={{ scale: 2 }}> <button onPointerDownCapture={e => e.stopPropagation()} /></motion.div>
#A note on SVG filters
The while
helper properties won't work on SVG filter
components, as these elements don't have a physical presence and therefore don't receive events. To respond to gestures, you need to introduce React state to the component and attach listeners to the physical element.
const MyComponent = () => { const [isHovered, setHovered] = useState(false)
// Simplified example return ( <svg> <image filter="url(#blur)" onMouseEnter={() => setHovered(true)} onMouseLeave={() => setHovered(false)} /> <filter id="blur"> <motion.div initial={false} animate={{ stdDeviation: isHovered ? 0 : 2 }} /> </filter> </svg> )}
#Hover
The hover gesture detects when a pointer hovers over or leaves a component.
It differs from onMouseEnter
and onMouseLeave
in that hover is guaranteed to only fire as a result of actual mouse events (as opposed to browser-generated mice events emulated from touch input).
<motion.a whileHover={{ scale: 1.2 }} onHoverStart={e => {}} onHoverEnd={e => {}}/>
#whileHover: VariantLabels | TargetAndTransition
Properties or variant label to animate to while the hover gesture is recognised.
<motion.div whileHover={{ scale: 1.2 }} />
#onHoverStart(event, info): void
Callback function that fires when pointer starts hovering over the component.
event: MouseEvent
info: EventInfo
<motion.div onHoverStart={() => console.log('Hover starts')} />
#onHoverEnd(event, info): void
Callback function that fires when pointer stops hovering over the component.
event: MouseEvent
info: EventInfo
<motion.div onHoverEnd={() => console.log("Hover ends")} />
#Focus
The focus gesture detects when a component gains or loses focus by the same rules as the CSS :focus-visible selector.
Typically, this is when an input
receives focus by any means, and when other elements receive focus by accessible means (like via keyboard navigation).
<motion.a whileFocus={{ scale: 1.2 }} href="#"/>
#whileFocus: VariantLabels | TargetAndTransition
Properties or variant label to animate to while the focus gesture is recognised.
<motion.input whileFocus={{ scale: 1.2 }} />
#Tap
The tap gesture detects when the primary pointer (like a left click or first touch point) presses down and releases on the same component.
It fires a tap
event when tapping successfully completes on a component, and a tapCancel
event when tapping ends outside the component.
If the tappable component is a child of a draggable component, it'll automatically cancel the tap gesture if the pointer moves further than 3 pixels during the gesture.
#Accessibility
Elements with tap events are keyboard-accessible.
Any element with a tap prop will be able to receive focus and Enter
can be used to trigger tap events on focused elements.
- Pressing
Enter
down will triggeronTapStart
andwhileTap
- Releasing
Enter
will triggeronTap
- If the element loses focus before
Enter
is released,onTapCancel
will fire.
#whileTap: VariantLabels | TargetAndTransition
Properties or variant label to animate to while the component is pressed.
<motion.div whileTap={{ scale: 0.8 }} />
#onTap(event, info): void
Callback when the tap gesture successfully ends on this element.
event: MouseEvent | TouchEvent | PointerEvent
The originating pointer event
info: TapInfo
A TapInfo
object containing x
and y
values for the point
relative to the device or page
function onTap(event, info) { console.log(info.point.x, info.point.y)}
<motion.div onTap={onTap} />
#onTapStart(event, info): void
Callback when the tap gesture starts on this element.
event: MouseEvent | TouchEvent | PointerEvent
The originating pointer event
info: TapInfo
A TapInfo
object containing x
and y
values for the point
relative to the device or page
function onTapStart(event, info) { console.log(info.point.x, info.point.y)}
<motion.div onTapStart={onTapStart} />
#onTapCancel(event, info): void
Callback when the tap gesture ends outside this element.
event: MouseEvent | TouchEvent | PointerEvent
The originating pointer event
info: TapInfo
A TapInfo
object containing x
and y
values for the point
relative to the device or page
function onTapCancel(event, info) { console.log(info.point.x, info.point.y)}
<motion.div onTapCancel={onTapCancel} />
#Pan
The pan gesture recognises when a pointer presses down on a component and moves further than 3 pixels. The pan gesture is ended when the pointer is released.
<motion.div onPan={(e, pointInfo) => {}} />
#onPan(event, info): void
Callback function that fires when the pan gesture is recognised on this element.
Note: For pan gestures to work correctly with touch input, the element needs touch scrolling to be disabled on either x/y or both axis with the touch-action
CSS rule.
event: MouseEvent | TouchEvent | PointerEvent
The originating pointer event
info: PanInfo
A PanInfo
object containing x
and y
values for:
point
: Relative to the device or page.delta
: Distance moved since the last event.offset
: Offset from the original pan event.velocity
: Current velocity of the pointer.
function onPan(event, info) { console.log(info.point.x, info.point.y)}
<motion.div onPan={onPan} />
#onPanStart(event, info): void
Callback function that fires when the pan gesture begins on this element.
event: MouseEvent | TouchEvent | PointerEvent
The originating pointer event
info: PanInfo
A PanInfo
object containing x
and y
values for:
point
: Relative to the device or page.delta
: Distance moved since the last event.offset
: Offset from the original pan event.velocity
: Current velocity of the pointer.
function onPanStart(event, info) { console.log(info.point.x, info.point.y)}
<motion.div onPanStart={onPanStart} />
#onPanEnd(event, info): void
Callback function that fires when the pan gesture ends on this element.
event: MouseEvent | TouchEvent | PointerEvent
The originating pointer event
info: PanInfo
A PanInfo
object containing x
and y
values for:
point
: Relative to the device or page.delta
: Distance moved since the last event.offset
: Offset from the original pan event.velocity
: Current velocity of the pointer.
function onPanEnd(event, info) { console.log(info.point.x, info.point.y)}
<motion.div onPanEnd={onPanEnd} />
#Drag
The drag gesture follows the rules of the pan gesture but applies pointer movement to the x and/or y axis of the component.
<motion.div drag />
#whileDrag: VariantLabels | TargetAndTransition
Properties or variant label to animate to while the drag gesture is recognised.
<motion.div whileDrag={{ scale: 1.2 }} />
#drag: boolean | "x" | "y"
Enable dragging for this element. Set to false
by default. Set true
to drag in both directions. Set "x"
or "y"
to only drag in a specific direction.
<motion.div drag="x" />
#dragConstraints: false | Partial<BoundingBox2D> | RefObject<Element>
Applies constraints on the permitted draggable area.
It can accept an object of optional top
, left
, right
, and bottom
values, measured in pixels. This will define a distance the named edge of the draggable component.
Alternatively, it can accept a ref
to another component created with React's useRef
hook. This ref
should be passed both to the draggable component's dragConstraints
prop, and the ref
of the component you want to use as constraints.
// In pixels<motion.div drag="x" dragConstraints={{ left: 0, right: 300 }}/>
// As a ref to another componentconst MyComponent = () => { const constraintsRef = useRef(null)
return ( <motion.div ref={constraintsRef}> <motion.div drag dragConstraints={constraintsRef} /> </motion.div> )}
#dragSnapToOrigin: boolean
If true
, the draggable element will animate back to its center/origin when released.
#dragElastic: DragElastic
The degree of movement allowed outside constraints. 0 = no movement, 1 = full movement.
Set to 0.5
by default. Can also be set as false
to disable movement.
By passing an object of top
/right
/bottom
/left
, individual values can be set per constraint. Any missing values will be set to 0
.
<motion.div drag dragConstraints={{ left: 0, right: 300 }} dragElastic={0.2}/>
#dragMomentum: boolean
Apply momentum from the pan gesture to the component when dragging finishes. Set to true
by default.
<motion.div drag dragConstraints={{ left: 0, right: 300 }} dragMomentum={false}/>
#dragTransition: InertiaOptions
Allows you to change dragging inertia parameters. When releasing a draggable Frame, an animation with type inertia
starts. The animation is based on your dragging velocity. This property allows you to customize it.
<motion.div drag dragTransition={{ bounceStiffness: 600, bounceDamping: 10 }}/>
#dragPropagation: boolean
Allows drag gesture propagation to child components. Set to false
by default.
<motion.div drag="x" dragPropagation />
#dragControls: DragControls
Usually, dragging is initiated by pressing down on a component and moving it. For some use-cases, for instance clicking at an arbitrary point on a video scrubber, we might want to initiate dragging from a different component than the draggable one.
By creating a dragControls
using the useDragControls
hook, we can pass this into the draggable component's dragControls
prop. It exposes a start
method that can start dragging from pointer events on other components.
const dragControls = useDragControls()
function startDrag(event) { dragControls.start(event, { snapToCursor: true })}
return ( <> <div onPointerDown={startDrag} /> <motion.div drag="x" dragControls={dragControls} /> </>)
Note: Given that by setting dragControls
you are taking control of initiating the drag gesture, it is possible to disable the draggable element as the initiator by setting dragListener={false}
.
#dragListener: boolean
Determines whether to trigger the drag gesture from event listeners. If passing dragControls
, setting this to false
will ensure dragging can only be initiated by the controls, rather than a pointerdown
event on the draggable element.
#onDrag(event, info): void
Callback function that fires when the component is dragged.
event: MouseEvent | TouchEvent | PointerEvent
info: PanInfo
<motion.div drag onDrag={ (event, info) => console.log(info.point.x, info.point.y) }/>
#onDragStart(event, info): void
Callback function that fires when dragging starts.
event: MouseEvent | TouchEvent | PointerEvent
info: PanInfo
<motion.div drag onDragStart={ (event, info) => console.log(info.point.x, info.point.y) }/>
#onDragEnd(event, info): void
Callback function that fires when dragging ends.
event: MouseEvent | TouchEvent | PointerEvent
info: PanInfo
<motion.div drag onDragEnd={ (event, info) => console.log(info.point.x, info.point.y) }/>
#onDirectionLock(axis): void
Callback function that fires a drag direction is determined.
axis: "x" | "y"
<motion.div drag dragDirectionLock onDirectionLock={axis => console.log(axis)}/>