Physics-based draggable system with momentum, constraints, snapping, and smooth interactions for creating intuitive drag interfaces.
Create draggable element with physics simulation and boundary constraints.
/**
* Create draggable element with physics and constraints
* @param target - Element to make draggable
* @param parameters - Draggable configuration
* @returns Draggable instance
*/
function createDraggable(
target: TargetsParam,
parameters?: DraggableParams
): Draggable;Usage Example:
import { createDraggable } from "animejs/draggable";
const draggable = createDraggable(".box", {
container: ".container",
onDrag: (self) => {
console.log(`Position: ${self.x}, ${self.y}`);
},
});Interactive drag functionality with physics simulation, constraints, and snapping.
/**
* Draggable class for physics-based drag interactions
*/
class Draggable {
// ===== Element Properties =====
/** Target element being dragged */
$target: HTMLElement;
/** Trigger element for drag interactions (defaults to target) */
$trigger: HTMLElement;
/** Container element for boundaries */
$container: HTMLElement;
// ===== Position Properties =====
/**
* Current X position in pixels
* @settable Set position directly
*/
x: number;
/**
* Current Y position in pixels
* @settable Set position directly
*/
y: number;
/**
* X position as progress (0-1) within container bounds
* 0 = left edge, 1 = right edge
* @settable Set position by progress
*/
progressX: number;
/**
* Y position as progress (0-1) within container bounds
* 0 = top edge, 1 = bottom edge
* @settable Set position by progress
*/
progressY: number;
/**
* X position change since last frame
*/
deltaX: number;
/**
* Y position change since last frame
*/
deltaY: number;
/**
* Current velocity magnitude
*/
velocity: number;
/**
* Drag angle in degrees
*/
angle: number;
// ===== State Properties =====
/**
* Whether element is currently grabbed (mousedown/touchstart)
*/
grabbed: boolean;
/**
* Whether element is actively being dragged
*/
dragged: boolean;
/**
* Whether element was just released
*/
released: boolean;
/**
* Whether draggable is enabled
*/
enabled: boolean;
// ===== Configuration Properties =====
/**
* Container padding constraints [top, right, bottom, left]
* Prevents dragging beyond these offsets from container edges
*/
containerPadding: [number, number, number, number];
/**
* Container edge friction (0-1)
* Higher values slow down near edges
*/
containerFriction: number;
/**
* X-axis snap points
* Number: snap to grid increment
* Array: snap to specific positions
*/
snapX: number | Array<number>;
/**
* Y-axis snap points
* Number: snap to grid increment
* Array: snap to specific positions
*/
snapY: number | Array<number>;
/**
* Drag speed multiplier
* Higher = faster drag response
*/
dragSpeed: number;
/**
* Minimum movement to activate drag (pixels)
* Prevents accidental drags from clicks
*/
dragThreshold: number;
/**
* Maximum velocity limit
*/
maxVelocity: number;
/**
* Minimum velocity before stopping
*/
minVelocity: number;
/**
* Cursor configuration
* boolean: enable/disable cursor styles
* object: custom cursor configuration
*/
cursor: boolean | DraggableCursorParams;
// ===== Callbacks =====
/**
* Called when element is grabbed (mousedown/touchstart)
*/
onGrab: Callback<Draggable>;
/**
* Called during drag movement
*/
onDrag: Callback<Draggable>;
/**
* Called when element is released
*/
onRelease: Callback<Draggable>;
/**
* Called on position update (drag, momentum, snap)
*/
onUpdate: Callback<Draggable>;
/**
* Called when element settles (velocity reaches zero)
*/
onSettle: Callback<Draggable>;
/**
* Called when element snaps to position
*/
onSnap: Callback<Draggable>;
/**
* Called when container resizes
*/
onResize: Callback<Draggable>;
/**
* Called after resize completes
*/
onAfterResize: Callback<Draggable>;
// ===== Methods =====
/**
* Set X position programmatically
* @param x - X position in pixels
* @param muteUpdateCallback - Skip onUpdate callback
* @returns Draggable instance for chaining
*/
setX(x: number, muteUpdateCallback?: boolean): Draggable;
/**
* Set Y position programmatically
* @param y - Y position in pixels
* @param muteUpdateCallback - Skip onUpdate callback
* @returns Draggable instance for chaining
*/
setY(y: number, muteUpdateCallback?: boolean): Draggable;
/**
* Refresh bounds and recalculate positions
* Call after layout changes
* @returns Draggable instance for chaining
*/
refresh(): Draggable;
/**
* Update draggable state and position
* @returns Draggable instance for chaining
*/
update(): Draggable;
/**
* Stop current drag and momentum
* @returns Draggable instance for chaining
*/
stop(): Draggable;
/**
* Scroll container to bring element into view
* @param duration - Scroll duration in ms
* @param gap - Gap from viewport edge
* @param ease - Easing function
* @returns Draggable instance for chaining
*/
scrollInView(duration?: number, gap?: number, ease?: EasingParam): Draggable;
/**
* Animate element into container view
* @param duration - Animation duration in ms
* @param gap - Gap from container edge
* @param ease - Easing function
* @returns Draggable instance for chaining
*/
animateInView(
duration?: number,
gap?: number,
ease?: EasingParam
): Draggable;
/**
* Reset to initial position
* @returns Draggable instance for chaining
*/
reset(): Draggable;
/**
* Enable draggable interactions
* @returns Draggable instance for chaining
*/
enable(): Draggable;
/**
* Disable draggable interactions
* @returns Draggable instance for chaining
*/
disable(): Draggable;
/**
* Revert and cleanup draggable
* Removes all event listeners and animations
* @returns Draggable instance for chaining
*/
revert(): Draggable;
}Usage Examples:
import { createDraggable } from "animejs/draggable";
// Basic draggable
const draggable = createDraggable(".box", {
onDrag: (self) => {
console.log(`Position: (${self.x}, ${self.y})`);
console.log(`Progress: (${self.progressX}, ${self.progressY})`);
},
});
// With container constraints
createDraggable(".card", {
container: ".container",
containerPadding: [10, 10, 10, 10], // Keep 10px from edges
onGrab: (self) => console.log("Grabbed!"),
onRelease: (self) => console.log("Released!"),
});
// With snapping
createDraggable(".tile", {
snapX: 100, // Snap to 100px grid on X
snapY: 100, // Snap to 100px grid on Y
onSnap: (self) => console.log("Snapped!"),
});
// Custom trigger element
createDraggable(".panel", {
trigger: ".panel-handle", // Only drag by handle
container: ".workspace",
});Configure boundaries and padding:
createDraggable(".element", {
container: ".container",
// Padding from container edges [top, right, bottom, left]
containerPadding: [20, 20, 20, 20],
// Edge friction (0-1)
containerFriction: 0.8, // Slows down near edges
});Snap to grid or specific positions:
// Grid snapping
createDraggable(".box", {
snapX: 50, // Snap to 50px grid on X axis
snapY: 50, // Snap to 50px grid on Y axis
});
// Specific positions
createDraggable(".box", {
snapX: [0, 100, 200, 300], // Snap to specific X positions
snapY: [0, 150, 300], // Snap to specific Y positions
onSnap: (self) => {
console.log(`Snapped to: ${self.x}, ${self.y}`);
},
});
// Mixed snapping
createDraggable(".box", {
snapX: 25, // Grid on X
snapY: [0, 100, 200, 300, 400], // Specific positions on Y
});Control drag feel and physics:
createDraggable(".element", {
// Speed multiplier
dragSpeed: 1.5, // Faster drag
// Activation threshold
dragThreshold: 5, // Must move 5px to start drag
// Velocity limits
maxVelocity: 50, // Cap maximum velocity
minVelocity: 0.1, // Stop below this velocity
// Container friction
containerFriction: 0.9, // Slow near edges
});Drag by handle or specific element:
// Drag entire card by header only
createDraggable(".card", {
trigger: ".card-header",
});
// Different trigger than target
createDraggable(".modal", {
trigger: ".modal-titlebar",
container: "body",
});Programmatically control position:
const draggable = createDraggable(".box");
// Set position directly
draggable.x = 100;
draggable.y = 200;
// Set by progress (0-1)
draggable.progressX = 0.5; // Center horizontally
draggable.progressY = 0.75; // 75% down
// Using methods
draggable.setX(150);
draggable.setY(250);
// Get current position
console.log(draggable.x, draggable.y);
console.log(draggable.progressX, draggable.progressY);
// Get movement delta
console.log(draggable.deltaX, draggable.deltaY);
// Get velocity and angle
console.log(draggable.velocity);
console.log(draggable.angle);Check and control draggable state:
const draggable = createDraggable(".element");
// Check state
console.log(draggable.grabbed); // Is grabbed?
console.log(draggable.dragged); // Is dragging?
console.log(draggable.released); // Just released?
console.log(draggable.enabled); // Is enabled?
// Control state
draggable.enable(); // Enable interactions
draggable.disable(); // Disable interactions
draggable.stop(); // Stop current drag
draggable.reset(); // Reset to start positionCustomize drag cursor:
// Simple enable/disable
createDraggable(".box", {
cursor: true, // Default cursor styles
});
// Custom cursor configuration
createDraggable(".box", {
cursor: {
grab: "grab",
grabbing: "grabbing",
},
});
// Disable cursor changes
createDraggable(".box", {
cursor: false,
});Bring element into view:
const draggable = createDraggable(".element", {
container: ".scroll-container",
});
// Scroll container to show element
draggable.scrollInView(500, 20, "outQuad");
// duration: 500ms
// gap: 20px from edge
// ease: outQuad
// Animate element into bounds
draggable.animateInView(300, 10, "outElastic");Update bounds after DOM changes:
const draggable = createDraggable(".element");
// After content loads or resizes
window.addEventListener("resize", () => {
draggable.refresh();
});
// After dynamic content
loadMoreContent().then(() => {
draggable.refresh();
});Complete drag interaction lifecycle:
createDraggable(".element", {
onGrab: (self) => {
console.log("User grabbed element");
console.log(`Start position: ${self.x}, ${self.y}`);
},
onDrag: (self) => {
console.log("Dragging...");
console.log(`Current: ${self.x}, ${self.y}`);
console.log(`Delta: ${self.deltaX}, ${self.deltaY}`);
console.log(`Velocity: ${self.velocity}`);
},
onRelease: (self) => {
console.log("Released");
console.log(`Final velocity: ${self.velocity}`);
},
onUpdate: (self) => {
console.log("Position updated (includes momentum)");
},
onSnap: (self) => {
console.log(`Snapped to: ${self.x}, ${self.y}`);
},
onSettle: (self) => {
console.log("Element settled (velocity = 0)");
},
onResize: (self) => {
console.log("Container resizing");
},
onAfterResize: (self) => {
console.log("Container resized");
},
});createDraggable(".slider-track", {
container: ".slider",
axis: "x", // X-axis only
snapX: (index) => index * 300, // Snap to slide width
onSnap: (self) => {
const slideIndex = Math.round(self.x / 300);
updateSlideIndicator(slideIndex);
},
});const draggable = createDraggable(".element", {
maxVelocity: 30,
minVelocity: 0.5,
onRelease: (self) => {
// Velocity automatically applies momentum
console.log(`Released with velocity: ${self.velocity}`);
},
onSettle: (self) => {
console.log("Momentum stopped");
},
});createDraggable(".slider-thumb", {
container: ".slider-track",
axis: "x", // Only horizontal
containerPadding: [0, 0, 0, 0],
onUpdate: (self) => {
// Update value based on progress
const value = self.progressX * 100;
updateSliderValue(value);
},
});createDraggable(".card", {
container: ".deck",
maxVelocity: 80, // Allow fast throws
onRelease: (self) => {
if (self.velocity > 50) {
// High velocity = swipe away
animateOut(self.$target);
}
},
});createDraggable(".disc", {
onUpdate: (self) => {
// Rotate based on drag angle
self.$target.style.transform = `
translate(${self.x}px, ${self.y}px)
rotate(${self.angle}deg)
`;
},
});/**
* Draggable configuration parameters
*/
interface DraggableParams {
/** Trigger element selector or element (defaults to target) */
trigger?: string | HTMLElement;
/** Container element for boundaries */
container?: string | HTMLElement | Array<number> | ((draggable: Draggable) => string | HTMLElement | Array<number>);
/** Enable/disable or configure X-axis dragging */
x?: boolean | DraggableAxisParam;
/** Enable/disable or configure Y-axis dragging */
y?: boolean | DraggableAxisParam;
/** Value modifier function */
modifier?: TweenModifier;
/** Snap points for both axes (grid size or array of positions) */
snap?: number | Array<number> | ((draggable: Draggable) => number | Array<number>);
/** Container padding [top, right, bottom, left] or single number for all sides */
containerPadding?: number | Array<number> | ((draggable: Draggable) => number | Array<number>);
/** Container edge friction (0-1) during active drag */
containerFriction?: number | ((draggable: Draggable) => number);
/** Container edge friction (0-1) during release momentum */
releaseContainerFriction?: number | ((draggable: Draggable) => number);
/** X-axis snap points (grid size or array of positions) */
snapX?: number | Array<number>;
/** Y-axis snap points (grid size or array of positions) */
snapY?: number | Array<number>;
/** Drag speed multiplier */
dragSpeed?: number | ((draggable: Draggable) => number);
/** Minimum movement to activate drag (pixels) */
dragThreshold?: number | DraggableDragThresholdParams | ((draggable: Draggable) => number | DraggableDragThresholdParams);
/** Auto-scroll speed when dragging near edges */
scrollSpeed?: number | ((draggable: Draggable) => number);
/** Distance from edge to trigger auto-scroll (pixels) */
scrollThreshold?: number | ((draggable: Draggable) => number);
/** Maximum velocity */
maxVelocity?: number | ((draggable: Draggable) => number);
/** Minimum velocity before stopping */
minVelocity?: number | ((draggable: Draggable) => number);
/** Velocity multiplier for momentum */
velocityMultiplier?: number | ((draggable: Draggable) => number);
/** Spring mass for release physics */
releaseMass?: number;
/** Spring stiffness for release physics */
releaseStiffness?: number;
/** Spring damping for release physics */
releaseDamping?: number;
/** Easing function for release animation */
releaseEase?: EasingParam;
/** Cursor styling configuration */
cursor?: boolean | DraggableCursorParams | ((draggable: Draggable) => boolean | DraggableCursorParams);
/** Called on grab */
onGrab?: Callback<Draggable>;
/** Called during drag */
onDrag?: Callback<Draggable>;
/** Called on release */
onRelease?: Callback<Draggable>;
/** Called on update */
onUpdate?: Callback<Draggable>;
/** Called when settled */
onSettle?: Callback<Draggable>;
/** Called on snap */
onSnap?: Callback<Draggable>;
/** Called on resize */
onResize?: Callback<Draggable>;
/** Called after resize */
onAfterResize?: Callback<Draggable>;
}
/**
* Axis-specific dragging configuration
*/
interface DraggableAxisParam {
/** Property to map axis value to */
mapTo?: string;
/** Value modifier function */
modifier?: TweenModifier;
/** Composition mode for transforms */
composition?: TweenComposition;
/** Snap points for this axis */
snap?: number | Array<number> | ((draggable: Draggable) => number | Array<number>);
}
/**
* Drag threshold configuration by input type
*/
interface DraggableDragThresholdParams {
/** Drag threshold for mouse input (pixels) */
mouse?: number;
/** Drag threshold for touch input (pixels) */
touch?: number;
}
/**
* Cursor configuration
*/
interface DraggableCursorParams {
/** Cursor style when hovering (not grabbed) */
onHover?: string;
/** Cursor style when grabbed/dragging */
onGrab?: string;
}
/**
* Callback function type
*/
type Callback<T> = (instance: T) => void;
/**
* Tween modifier function type
*/
type TweenModifier = (value: any) => any;
/**
* Tween composition mode type
*/
type TweenComposition = "none" | "replace" | "blend";