Drag controls and gesture event handlers for interactive animations and user input handling.
Creates controls for programmatically managing drag operations.
/**
* Creates drag controls for manual drag management
* @returns DragControls instance
*/
function useDragControls(): DragControls;
interface DragControls {
/**
* Start a drag operation
* @param event - Pointer event that triggered the drag
* @param options - Drag start options
*/
start(event: React.PointerEvent | PointerEvent, options?: StartOptions): void;
/**
* Cancel the current drag operation
*/
cancel(): void;
/**
* Stop the current drag operation
*/
stop(): void;
/**
* Subscribe to drag controls updates
* @param controls - Another drag controls instance to link
*/
subscribe(controls: DragControls): () => void;
}
interface StartOptions {
/**
* Snap back to original position when drag ends
*/
snapToCursor?: boolean;
/**
* Cursor to use during drag
*/
cursorProgress?: Point;
}
interface Point {
x: number;
y: number;
}Usage Examples:
import { motion, useDragControls } from "framer-motion";
import { useRef } from "react";
// Basic drag controls
function DragControlsExample() {
const controls = useDragControls();
return (
<div>
<div
className="w-8 h-8 bg-blue-500 cursor-grab rounded-full mb-4"
onPointerDown={(e) => controls.start(e)}
>
Drag Handle
</div>
<motion.div
drag
dragControls={controls}
className="w-24 h-24 bg-red-500 rounded"
>
Draggable Element
</motion.div>
</div>
);
}
// Multiple linked drag controls
function LinkedDragExample() {
const controls1 = useDragControls();
const controls2 = useDragControls();
// Link controls so they move together
React.useEffect(() => {
const unsubscribe = controls1.subscribe(controls2);
return unsubscribe;
}, [controls1, controls2]);
return (
<div className="space-y-4">
<motion.div
drag
dragControls={controls1}
className="w-24 h-24 bg-blue-500 rounded"
>
Element 1
</motion.div>
<motion.div
drag
dragControls={controls2}
className="w-24 h-24 bg-green-500 rounded"
>
Element 2 (linked)
</motion.div>
</div>
);
}Motion components support comprehensive drag functionality through props.
interface DragProps {
/**
* Enable dragging on x, y, or both axes
*/
drag?: boolean | "x" | "y";
/**
* Drag controls instance for manual control
*/
dragControls?: DragControls;
/**
* Constraints for drag boundaries
*/
dragConstraints?: Partial<BoundingBox2D> | React.RefObject<Element>;
/**
* Enable elastic drag beyond constraints
*/
dragElastic?: boolean | number;
/**
* Enable momentum and inertia after drag ends
*/
dragMomentum?: boolean;
/**
* Transition for snapping back to constraints
*/
dragTransition?: {
bounceStiffness?: number;
bounceDamping?: number;
power?: number;
timeConstant?: number;
restDelta?: number;
modifyTarget?: (target: number) => number;
};
/**
* Propagate drag to parent elements
*/
dragPropagation?: boolean;
/**
* Listen for external drag events
*/
dragListener?: boolean;
/**
* Direction lock threshold
*/
dragDirectionLock?: boolean;
/**
* Minimum distance before drag starts
*/
dragSnapToOrigin?: boolean;
}
interface BoundingBox2D {
top: number;
right: number;
bottom: number;
left: number;
}Drag Event Handlers:
interface DragEventHandlers {
/**
* Called when drag operation starts
*/
onDragStart?: (
event: MouseEvent | TouchEvent | PointerEvent,
info: PanInfo
) => void;
/**
* Called continuously during drag
*/
onDrag?: (
event: MouseEvent | TouchEvent | PointerEvent,
info: PanInfo
) => void;
/**
* Called when drag operation ends
*/
onDragEnd?: (
event: MouseEvent | TouchEvent | PointerEvent,
info: PanInfo
) => void;
/**
* Called when dragging beyond constraints (elastic)
*/
onDirectionLock?: (axis: "x" | "y") => void;
}
interface PanInfo {
/**
* Current pointer position relative to page
*/
point: Point;
/**
* Change in position since last event
*/
delta: Point;
/**
* Total offset from drag start position
*/
offset: Point;
/**
* Current velocity of the pointer
*/
velocity: Point;
}Usage Examples:
import { motion } from "framer-motion";
import { useRef, useState } from "react";
// Constrained dragging
function ConstrainedDragExample() {
const constraintsRef = useRef<HTMLDivElement>(null);
return (
<div
ref={constraintsRef}
className="w-96 h-96 bg-gray-200 relative border-2 border-gray-400"
>
<motion.div
drag
dragConstraints={constraintsRef}
dragElastic={0.2}
className="w-16 h-16 bg-blue-500 rounded cursor-grab"
>
Constrained
</motion.div>
</div>
);
}
// Axis-specific dragging with momentum
function AxisDragExample() {
return (
<div className="space-y-8">
<motion.div
drag="x"
dragMomentum={true}
dragConstraints={{ left: 0, right: 300 }}
className="w-16 h-16 bg-red-500 rounded cursor-grab"
>
X-axis only
</motion.div>
<motion.div
drag="y"
dragMomentum={false}
dragConstraints={{ top: 0, bottom: 200 }}
className="w-16 h-16 bg-green-500 rounded cursor-grab"
>
Y-axis only
</motion.div>
</div>
);
}
// Drag with custom transitions and events
function AdvancedDragExample() {
const [dragInfo, setDragInfo] = useState<PanInfo | null>(null);
return (
<div>
<motion.div
drag
dragConstraints={{ left: -200, right: 200, top: -200, bottom: 200 }}
dragTransition={{
bounceStiffness: 300,
bounceDamping: 20,
power: 0.2,
modifyTarget: (target) => Math.round(target / 50) * 50 // Snap to grid
}}
onDragStart={(event, info) => {
console.log("Drag started", info);
}}
onDrag={(event, info) => {
setDragInfo(info);
}}
onDragEnd={(event, info) => {
console.log("Drag ended", info);
setDragInfo(null);
}}
className="w-16 h-16 bg-purple-500 rounded cursor-grab"
>
Advanced Drag
</motion.div>
{dragInfo && (
<div className="mt-4 text-sm">
<p>Position: {Math.round(dragInfo.point.x)}, {Math.round(dragInfo.point.y)}</p>
<p>Velocity: {Math.round(dragInfo.velocity.x)}, {Math.round(dragInfo.velocity.y)}</p>
<p>Offset: {Math.round(dragInfo.offset.x)}, {Math.round(dragInfo.offset.y)}</p>
</div>
)}
</div>
);
}Handle mouse hover interactions with motion components.
interface HoverEventHandlers {
/**
* Called when mouse enters element
*/
onHoverStart?: (event: MouseEvent, info: EventInfo) => void;
/**
* Called when mouse leaves element
*/
onHoverEnd?: (event: MouseEvent, info: EventInfo) => void;
}
interface EventInfo {
/**
* Current pointer position
*/
point: Point;
}Usage Example:
import { motion } from "framer-motion";
import { useState } from "react";
function HoverExample() {
const [hoverInfo, setHoverInfo] = useState<string>("");
return (
<motion.div
whileHover={{ scale: 1.1, rotate: 5 }}
onHoverStart={(event, info) => {
setHoverInfo(`Hover started at ${info.point.x}, ${info.point.y}`);
}}
onHoverEnd={(event, info) => {
setHoverInfo(`Hover ended at ${info.point.x}, ${info.point.y}`);
}}
className="w-32 h-32 bg-blue-500 rounded cursor-pointer flex items-center justify-center text-white"
>
Hover me
{hoverInfo && <div className="absolute top-full mt-2 text-sm text-black">{hoverInfo}</div>}
</motion.div>
);
}Handle tap/click interactions with detailed gesture information.
interface TapEventHandlers {
/**
* Called when tap/click occurs
*/
onTap?: (event: MouseEvent | TouchEvent | PointerEvent, info: TapInfo) => void;
/**
* Called when tap/click starts (pointer down)
*/
onTapStart?: (event: MouseEvent | TouchEvent | PointerEvent, info: TapInfo) => void;
/**
* Called when tap is cancelled (pointer moved too far)
*/
onTapCancel?: (event: MouseEvent | TouchEvent | PointerEvent, info: TapInfo) => void;
}
interface TapInfo {
/**
* Current pointer position
*/
point: Point;
}Usage Example:
import { motion } from "framer-motion";
import { useState } from "react";
function TapExample() {
const [tapCount, setTapCount] = useState(0);
const [lastTapPoint, setLastTapPoint] = useState<Point | null>(null);
return (
<motion.button
whileTap={{ scale: 0.95 }}
onTap={(event, info) => {
setTapCount(count => count + 1);
setLastTapPoint(info.point);
}}
onTapStart={(event, info) => {
console.log("Tap started at", info.point);
}}
onTapCancel={(event, info) => {
console.log("Tap cancelled at", info.point);
}}
className="px-6 py-3 bg-green-500 text-white rounded hover:bg-green-600 focus:outline-none focus:ring-2 focus:ring-green-400"
>
Tap me (count: {tapCount})
{lastTapPoint && (
<div className="text-xs mt-1">
Last tap: {Math.round(lastTapPoint.x)}, {Math.round(lastTapPoint.y)}
</div>
)}
</motion.button>
);
}Handle focus and blur events for accessibility and keyboard navigation.
interface FocusEventHandlers {
/**
* Called when element receives focus
*/
onFocus?: (event: FocusEvent) => void;
/**
* Called when element loses focus
*/
onBlur?: (event: FocusEvent) => void;
}Usage Example:
import { motion } from "framer-motion";
import { useState } from "react";
function FocusExample() {
const [isFocused, setIsFocused] = useState(false);
return (
<motion.input
type="text"
placeholder="Focus me"
whileFocus={{ scale: 1.05, borderColor: "#3B82F6" }}
onFocus={() => setIsFocused(true)}
onBlur={() => setIsFocused(false)}
className="px-4 py-2 border-2 border-gray-300 rounded outline-none transition-colors"
style={{
borderColor: isFocused ? "#3B82F6" : "#D1D5DB"
}}
/>
);
}Utilities for handling DOM events in a cross-platform way.
/**
* Add pointer event listener with proper cross-platform handling
* @param element - Element to add listener to
* @param event - Event type to listen for
* @param handler - Event handler function
* @param options - Event listener options
* @returns Cleanup function
*/
function addPointerEvent(
element: Element,
event: string,
handler: (event: PointerEvent) => void,
options?: AddEventListenerOptions
): () => void;
/**
* Add pointer info to event handlers for consistent data structure
* @param handler - Original event handler
* @returns Enhanced handler with pointer info
*/
function addPointerInfo<T extends Event>(
handler: (event: T, info: EventInfo) => void
): (event: T) => void;
/**
* React hook for DOM event handling
* @param ref - Element reference
* @param event - Event type
* @param handler - Event handler
* @param options - Event options
*/
function useDomEvent(
ref: React.RefObject<Element>,
event: string,
handler: (event: Event) => void,
options?: AddEventListenerOptions
): void;Usage Example:
import { useDomEvent } from "framer-motion";
import { useRef, useEffect } from "react";
function DOMEventExample() {
const ref = useRef<HTMLDivElement>(null);
// React hook approach
useDomEvent(ref, "pointerdown", (event) => {
console.log("Pointer down:", event);
});
// Manual approach
useEffect(() => {
const element = ref.current;
if (!element) return;
const cleanup = addPointerEvent(
element,
"pointermove",
(event) => {
console.log("Pointer move:", event.clientX, event.clientY);
},
{ passive: true }
);
return cleanup;
}, []);
return (
<div
ref={ref}
className="w-64 h-64 bg-gray-200 border-2 border-gray-400 flex items-center justify-center"
>
Interactive area
</div>
);
}Drag with Hover:
<motion.div
drag
whileHover={{ scale: 1.05 }}
whileDrag={{ scale: 1.1, zIndex: 10 }}
onDragStart={() => console.log("Started dragging")}
onHoverStart={() => console.log("Started hovering")}
>
Combined gestures
</motion.div>Conditional Gestures:
const [isDraggable, setIsDraggable] = useState(true);
<motion.div
drag={isDraggable}
dragConstraints={isDraggable ? { left: 0, right: 300 } : false}
whileTap={!isDraggable ? { scale: 0.95 } : undefined}
onTap={() => {
if (!isDraggable) {
console.log("Clicked instead of dragged");
}
}}
>
Conditional interaction
</motion.div>Multi-Touch Support:
<motion.div
drag
onPanStart={(event, info) => {
// Handle touch/pan start
console.log("Pan started with", info.point);
}}
onPan={(event, info) => {
// Handle continuous touch/pan
console.log("Panning:", info.delta);
}}
onPanEnd={(event, info) => {
// Handle touch/pan end
console.log("Pan ended with velocity:", info.velocity);
}}
>
Multi-touch support
</motion.div>