High-performance JavaScript animation library for animating CSS, SVG, canvas, React, Vue, WebGL, and any JavaScript-accessible properties.
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
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()
});
};