Draggable adds drag and drop functionality to elements with momentum, bounds checking, collision detection, and smooth interactions. It integrates seamlessly with GSAP animations.
import { Draggable } from "gsap/Draggable";
gsap.registerPlugin(Draggable);Create draggable elements with comprehensive configuration options.
/**
* Create Draggable instances for elements
* @param targets - Elements to make draggable
* @param vars - Draggable configuration
* @returns Array of Draggable instances
*/
Draggable.create(targets: string | Element | Element[], vars?: Draggable.Vars): Draggable[];
interface Draggable.Vars {
// Drag type
type?: "x" | "y" | "rotation" | "top,left" | "x,y";
// Bounds
bounds?: Element | string | { minX?: number; maxX?: number; minY?: number; maxY?: number; };
// Behavior
edgeResistance?: number; // Resistance when hitting bounds (0-1)
throwResistance?: number; // Momentum decay resistance (0-10000)
throwProps?: boolean; // Enable momentum throwing
// Snapping
snap?: object | Function; // Snap configuration
// Interaction
trigger?: Element | string; // Element that triggers drag (if different from target)
cursor?: string; // CSS cursor during drag
zIndexBoost?: boolean; // Boost z-index during drag
// Callbacks
onPress?: Function; // Called when press starts
onDragStart?: Function; // Called when drag starts
onDrag?: Function; // Called during drag
onDragEnd?: Function; // Called when drag ends
onRelease?: Function; // Called when released
onThrowUpdate?: Function; // Called during momentum throw
onThrowComplete?: Function; // Called when throw completes
}Usage Examples:
// Basic draggable
Draggable.create(".box", {
type: "x,y" // Allow dragging in both directions
});
// Constrained dragging
Draggable.create(".slider-handle", {
type: "x", // Horizontal only
bounds: ".slider-track", // Constrain to track element
edgeResistance: 0.8
});
// With momentum
Draggable.create(".card", {
type: "x,y",
throwProps: true,
bounds: window,
edgeResistance: 0.65,
throwResistance: 300
});Properties available on Draggable instances for state and position information.
interface Draggable {
// Position
x: number; // Current x position
y: number; // Current y position
rotation: number; // Current rotation (for rotation type)
// Drag state
isDragging: boolean; // Currently being dragged
isPressed: boolean; // Mouse/touch is pressed
isThrowing: boolean; // Currently in momentum throw
// Start position
startX: number; // X position when drag started
startY: number; // Y position when drag started
// Deltas
deltaX: number; // Change in X since drag start
deltaY: number; // Change in Y since drag start
// Pointer info
pointerX: number; // Current pointer X position
pointerY: number; // Current pointer Y position
pointerEvent: Event; // Current pointer event
// Target info
target: Element; // The draggable element
// Methods
disable(): void; // Disable dragging
enable(): void; // Enable dragging
endDrag(): void; // Force end current drag
kill(): void; // Destroy the Draggable
update(): void; // Update bounds and calculations
}Usage Examples:
const draggable = Draggable.create(".box")[0];
// Access position
console.log(`Position: ${draggable.x}, ${draggable.y}`);
// Check state
if (draggable.isDragging) {
console.log("Currently dragging");
}
// Control draggable
draggable.disable(); // Temporarily disable
draggable.enable(); // Re-enable
draggable.kill(); // Clean up when doneUtility methods available on the Draggable class.
/**
* Get existing Draggable instance for an element
* @param target - Element to get Draggable for
* @returns Draggable instance or null
*/
Draggable.get(target: Element): Draggable | null;
/**
* Hit test between two objects
* @param obj1 - First object (Element or object with x,y,width,height)
* @param obj2 - Second object
* @param threshold - Overlap threshold (0-100, as percentage)
* @returns True if objects overlap
*/
Draggable.hitTest(obj1: any, obj2: any, threshold?: number): boolean;
/**
* Get time since last drag ended (in milliseconds)
* @returns Time since last drag
*/
Draggable.timeSinceDrag(): number;Usage Examples:
// Get existing Draggable
const existingDraggable = Draggable.get(".box");
if (existingDraggable) {
existingDraggable.disable();
}
// Collision detection
const box1 = document.querySelector(".box1");
const box2 = document.querySelector(".box2");
if (Draggable.hitTest(box1, box2, 50)) {
console.log("Boxes are overlapping by at least 50%");
}
// Check recent drag activity
if (Draggable.timeSinceDrag() < 1000) {
console.log("Something was dragged recently");
}Different types of dragging behavior available.
type DragType =
| "x" // Horizontal dragging only
| "y" // Vertical dragging only
| "x,y" // Free dragging in both directions
| "top,left" // Drag using CSS top/left instead of transforms
| "rotation"; // Rotational dragging around center pointUsage Examples:
// Horizontal slider
Draggable.create(".h-slider", { type: "x" });
// Vertical slider
Draggable.create(".v-slider", { type: "y" });
// Free movement
Draggable.create(".free-box", { type: "x,y" });
// Rotational knob
Draggable.create(".knob", {
type: "rotation",
bounds: { minRotation: 0, maxRotation: 270 }
});
// Layout properties (for special cases)
Draggable.create(".positioned", { type: "top,left" });Configure boundaries and movement constraints.
interface BoundsConfig {
// Element bounds
bounds?: Element | string; // Constrain within element
// Numeric bounds
bounds?: {
minX?: number;
maxX?: number;
minY?: number;
maxY?: number;
minRotation?: number;
maxRotation?: number;
};
// Special bounds
bounds?: Window; // Constrain to viewport
}Usage Examples:
// Constrain to parent element
Draggable.create(".child", {
type: "x,y",
bounds: ".parent"
});
// Numeric constraints
Draggable.create(".constrained", {
type: "x,y",
bounds: { minX: 0, maxX: 500, minY: 0, maxY: 300 }
});
// Viewport bounds
Draggable.create(".viewport-bound", {
type: "x,y",
bounds: window
});
// Rotation bounds
Draggable.create(".dial", {
type: "rotation",
bounds: { minRotation: -180, maxRotation: 180 }
});Configure snapping behavior for precise positioning.
interface SnapConfig {
snap?: {
x?: number | number[] | Function; // X snap increments/positions
y?: number | number[] | Function; // Y snap increments/positions
rotation?: number | number[]; // Rotation snap increments
};
}Usage Examples:
// Grid snapping
Draggable.create(".grid-item", {
type: "x,y",
snap: {
x: 50, // Snap to multiples of 50px
y: 50
}
});
// Specific positions
Draggable.create(".card", {
type: "x,y",
snap: {
x: [0, 100, 200, 300], // Snap to specific X positions
y: [0, 150, 300] // Snap to specific Y positions
}
});
// Function-based snapping
Draggable.create(".custom-snap", {
type: "x,y",
snap: {
x: function(endValue) {
return Math.round(endValue / 25) * 25; // Custom snap logic
}
}
});Handle drag lifecycle events for interactive feedback.
interface DragCallbacks {
onPress?: (event: Event) => void; // Mouse/touch press
onDragStart?: (event: Event) => void; // Drag starts (after threshold)
onDrag?: (event: Event) => void; // During drag
onDragEnd?: (event: Event) => void; // Drag ends
onRelease?: (event: Event) => void; // Mouse/touch release
onThrowUpdate?: (event: Event) => void; // During momentum
onThrowComplete?: (event: Event) => void; // Momentum complete
}Usage Examples:
Draggable.create(".interactive", {
type: "x,y",
onPress: function(event) {
gsap.to(this.target, { scale: 1.1, duration: 0.1 });
},
onDragStart: function(event) {
console.log("Drag started");
gsap.to(this.target, { rotation: 5, duration: 0.2 });
},
onDrag: function(event) {
// Update other elements based on drag position
gsap.set(".shadow", { x: this.x + 10, y: this.y + 10 });
},
onDragEnd: function(event) {
gsap.to(this.target, { scale: 1, rotation: 0, duration: 0.3 });
},
onRelease: function(event) {
console.log("Released");
}
});Combining Draggable with GSAP animations for enhanced interactions.
// Animate to position after drag
const draggable = Draggable.create(".box", {
type: "x,y",
onDragEnd: function() {
// Animate to nearest corner
const corners = [
{ x: 0, y: 0 },
{ x: 300, y: 0 },
{ x: 300, y: 200 },
{ x: 0, y: 200 }
];
let closest = corners[0];
let minDistance = Infinity;
corners.forEach(corner => {
const distance = Math.sqrt(
Math.pow(this.x - corner.x, 2) +
Math.pow(this.y - corner.y, 2)
);
if (distance < minDistance) {
minDistance = distance;
closest = corner;
}
});
gsap.to(this.target, {
x: closest.x,
y: closest.y,
duration: 0.5,
ease: "back.out(1.7)"
});
}
});
// Disable dragging during animation
const animateAndDisable = () => {
draggable[0].disable();
gsap.to(".box", {
x: 200,
y: 100,
duration: 1,
onComplete: () => draggable[0].enable()
});
};