Dragula is a lightweight JavaScript drag and drop library that provides an intuitive API for implementing drag and drop functionality in web applications. It features automatic sort order detection, visual feedback through shadows, touch event support, seamless click handling, and broad browser compatibility including IE7+.
npm install dragulaconst dragula = require('dragula');ES modules:
import dragula from 'dragula';You'll also need to include the CSS:
<link rel="stylesheet" href="node_modules/dragula/dist/dragula.css">Or with bundlers:
import 'dragula/dist/dragula.css';import dragula from 'dragula';
// Basic setup with containers
const containers = [
document.querySelector('#left'),
document.querySelector('#right')
];
const drake = dragula(containers);
// With options
const drake = dragula(containers, {
copy: true,
revertOnSpill: false,
removeOnSpill: true,
direction: 'vertical'
});
// Listen to events
drake.on('drop', (el, target, source, sibling) => {
console.log('Element dropped:', el);
console.log('Into container:', target);
console.log('From container:', source);
console.log('Before sibling:', sibling);
});
// Manual drag operations
drake.start(element); // Start dragging
drake.end(); // End drag
drake.cancel(); // Cancel drag
drake.remove(); // Remove elementCreates a new drake instance with drag and drop functionality.
/**
* Create a drake instance for drag and drop operations
* @param {HTMLElement[]} containers - Array of DOM elements that act as drag containers
* @param {DragulaOptions} options - Configuration options
* @returns {Drake} Drake instance
*/
function dragula(containers, options);The drake instance returned by the dragula factory function.
interface Drake {
/** Array of container elements for this drake instance */
containers: HTMLElement[];
/** Whether an element is currently being dragged */
dragging: boolean;
/** Manually start dragging an element */
start(item: HTMLElement): void;
/** End the current drag operation gracefully */
end(): void;
/** Cancel the current drag operation */
cancel(revert?: boolean): void;
/** Remove the currently dragged element from DOM */
remove(): void;
/** Clean up all event listeners and destroy the drake */
destroy(): void;
/** Check if an element can be moved by this drake */
canMove(item: HTMLElement): boolean;
/** Register event listener */
on(eventName: string, listener: Function): Drake;
/** Register one-time event listener */
once(eventName: string, listener: Function): Drake;
/** Remove event listeners */
off(eventName?: string, listener?: Function): Drake;
/** Emit an event */
emit(eventName: string, ...args: any[]): Drake;
}Complete configuration options for customizing drag and drop behavior.
interface DragulaOptions {
/** Array of container elements (alternative to first parameter) */
containers?: HTMLElement[];
/** Function to determine if element is a container */
isContainer?: (el: HTMLElement) => boolean;
/** Function to determine if element can be moved */
moves?: (el: HTMLElement, source: HTMLElement, handle: HTMLElement, sibling: HTMLElement) => boolean;
/** Function to determine if element can be dropped in target */
accepts?: (el: HTMLElement, target: HTMLElement, source: HTMLElement, sibling: HTMLElement) => boolean;
/** Function to prevent drag from starting */
invalid?: (el: HTMLElement, handle: HTMLElement) => boolean;
/** Copy elements instead of moving them */
copy?: boolean | ((el: HTMLElement, source: HTMLElement) => boolean);
/** Allow sorting in copy-source containers */
copySortSource?: boolean;
/** Return element to original position when spilled */
revertOnSpill?: boolean;
/** Remove element from DOM when spilled */
removeOnSpill?: boolean;
/** Direction for determining drop position ('vertical' or 'horizontal') */
direction?: 'vertical' | 'horizontal';
/** Container for mirror elements during drag */
mirrorContainer?: HTMLElement;
/** Allow text selection in input elements */
ignoreInputTextSelection?: boolean;
/** X-axis movement threshold before drag starts */
slideFactorX?: number;
/** Y-axis movement threshold before drag starts */
slideFactorY?: number;
}Comprehensive event system for tracking drag and drop operations.
// Event listener registration
drake.on(eventName, listener);
// Available events:
/** Element was lifted from source container */
'drag': (el: HTMLElement, source: HTMLElement) => void;
/** Drag operation ended */
'dragend': (el: HTMLElement) => void;
/** Element was dropped into target container */
'drop': (el: HTMLElement, target: HTMLElement, source: HTMLElement, sibling: HTMLElement | null) => void;
/** Element was returned to original container */
'cancel': (el: HTMLElement, container: HTMLElement, source: HTMLElement) => void;
/** Element was removed from DOM */
'remove': (el: HTMLElement, container: HTMLElement, source: HTMLElement) => void;
/** Visual shadow element was moved */
'shadow': (el: HTMLElement, container: HTMLElement, source: HTMLElement) => void;
/** Element is over a container */
'over': (el: HTMLElement, container: HTMLElement, source: HTMLElement) => void;
/** Element was dragged out of container */
'out': (el: HTMLElement, container: HTMLElement, source: HTMLElement) => void;
/** Element was cloned (mirror or copy) */
'cloned': (clone: HTMLElement, original: HTMLElement, type: 'mirror' | 'copy') => void;Event Usage Examples:
const drake = dragula([container1, container2]);
// Track when items are dropped
drake.on('drop', (el, target, source, sibling) => {
console.log(`Moved ${el.textContent} from ${source.id} to ${target.id}`);
if (sibling) {
console.log(`Placed before ${sibling.textContent}`);
}
});
// Handle drag start
drake.on('drag', (el, source) => {
el.classList.add('is-moving');
});
// Clean up after drag ends
drake.on('dragend', (el) => {
el.classList.remove('is-moving');
});
// Handle copies
drake.on('cloned', (clone, original, type) => {
if (type === 'copy') {
clone.id = original.id + '-copy';
}
});
// One-time event listener
drake.once('drop', (el, target) => {
console.log('First drop completed!');
});
// Remove specific event listener
function myDragHandler(el, source) {
console.log('Dragging started');
}
drake.on('drag', myDragHandler);
drake.off('drag', myDragHandler); // Remove specific handler
// Remove all listeners for an event type
drake.off('drop'); // Remove all drop listeners
// Remove all listeners for all events
drake.off(); // Clean up everythingDragula applies CSS classes during drag operations for styling.
/* Applied to mirror container during drag */
.gu-unselectable {
/* Prevents text selection */
}
/* Applied to source element during drag */
.gu-transit {
/* Typically adds opacity */
}
/* Applied to mirror/ghost element */
.gu-mirror {
/* Fixed positioning and z-index */
}
/* Helper class for hiding elements */
.gu-hide {
display: none !important;
}Dynamic Container Management:
const drake = dragula();
// Add containers dynamically
drake.containers.push(document.querySelector('#new-container'));
// Remove containers
const index = drake.containers.indexOf(oldContainer);
if (index > -1) {
drake.containers.splice(index, 1);
}Copy vs Move Behavior:
const drake = dragula([sourceContainer, targetContainer], {
copy: (el, source) => {
// Copy from source, move within target
return source === sourceContainer;
},
accepts: (el, target, source, sibling) => {
// Don't allow copies back to source
return target !== sourceContainer;
}
});Custom Drag Handles:
const drake = dragula([container], {
moves: (el, source, handle, sibling) => {
// Only allow dragging by elements with 'drag-handle' class
return handle.classList.contains('drag-handle');
}
});Touch and Mobile Support:
// Dragula automatically handles touch events
const drake = dragula([container], {
// Adjust slide factors for better touch experience
slideFactorX: 5,
slideFactorY: 5
});Validation and Restrictions:
const drake = dragula([container1, container2], {
accepts: (el, target, source, sibling) => {
// Custom drop validation logic
return target.dataset.accepts === el.dataset.type;
},
invalid: (el, handle) => {
// Prevent dragging certain elements
return el.classList.contains('no-drag');
}
});