Draggable
Draggable class acts as an orchestrator for any amount of sensors and moves DOM elements based on drag events emitted by sensors.
Example
import {
PointerSensor,
KeyboardSensor,
Draggable,
createPointerSensorStartPredicate,
} from 'dragdoll';
const element = document.querySelector('.draggable') as HTMLElement;
const pointerSensor = new PointerSensor(element);
const keyboardSensor = new KeyboardSensor(element);
const draggable = new Draggable([pointerSensor, keyboardSensor], {
elements: () => [element],
startPredicate: createPointerSensorStartPredicate(),
});
Constructor
class Draggable {
constructor(sensors: Sensor[], options?: Partial<DraggableSettings>) {}
}
Parameters
sensors
- An array of sensors that the Draggable will use as inputs for moving the provided elements around. The sensors are required and can't be changed after instantiation.
options
- An optional
DraggableSettings
object, which you can also change later viaupdateSettings
method. You only need to provide the options you want to change, the rest will be left as default.
- An optional
Settings
Draggable settings is an object with the following properties.
container
type container = HTMLElement | null;
The element the dragged elements should be appended to for the duration of the drag. If set to null
the element's current parent element is used.
Default is null
.
INFO
When using a custom container, the dragged element must be either absolute
or fixed
positioned. You can overcome this limitation by changing the element's CSS position to absolute
or fixed
just before the drag starts (e.g. using elements
) and then back to the original position after the drag ends. Additionally you will need to offset the element's position to match the original position before the drag starts, e.g. by using transform: translate(x, y)
.
startPredicate
type startPredicate = (data: {
draggable: Draggable;
sensor: Sensor;
event: SensorStartEvent | SensorMoveEvent;
}) => boolean | undefined;
A function that determines if drag should start or not. An important thing to note is that each sensor attached to the draggable has their own start predicate state. So if you reject the predicate for sensorA then sensorB can still keep trying to resolve its start predicate. However, once any sensor's start predicate is resolved the other sensors' start predicates are automatically rejected.
Return:
true
to resolve the predicate and start drag.false
to reject the predicate and prevent drag from starting during the sensor's current drag process.undefined
to keep the predicate in pending state and try resolving it again after the next "move" event from the sensor.
Default is () => true
.
elements
type elements = (data: {
draggable: Draggable;
drag: DraggableDrag;
}) => HTMLElement[] | null;
A function that should return all the elements you want to move during the drag.
Default is () => null
.
frozenStyles
type frozenStyles: (data: {
draggable: Draggable;
drag: DraggableDrag;
item: DraggableDragItem;
style: CSSStyleDeclaration;
}) => string[] | {[key: string]: string} | null;
A function that should return the an array of CSS properties that should be frozen during the drag. By "frozen" we mean that the current computed value of the property is stored and applied to the element's inline styles during the drag. This is usually only needed if you have a drag container and the dragged element has percentage based values for some of it's properties. The frozen properties are automatically unfrozen (restored to the original values) when drag ends.
You can also return an object with key-value pairs where the key is the CSS property you want to freeze and the value is the explicit value you want it to be frozen to. This is useful if you want to freeze a property to a specific value instead of the current computed value.
By default nothing is frozen.
applyPosition
type applyPosition = (data: {
draggable: Draggable;
drag: DraggableDrag;
item: DraggableDragItem;
phase: 'start' | 'start-align' | 'move' | 'align' | 'end' | 'end-align';
}) => void;
A function that should apply the current position
to a dragged element
.
Default is a (very involved) function that applies the position, container offset, alignment offset and matrix transform offsets the element's transform property, while respecting the element's original transform and transform origin.
Also note that the phase
argument is provided to the function to help you determine what phase of the drag process you are in:
start
: Called when the drag starts.start-align
: Called during the start process afterstart
phase if the element is moved within a drag container AND the drag container and the element container have different world matrices.move
: Called on every "move" event emitted by the currently tracked sensor during the drag.align
: Called when the element's position is realigned (viaalign
method).end
: Called when the drag ends.end-align
: Called after theend
phase if the element was moved back to the original container from the drag container AND the element's position needs to be realigned to match the current client position.
positionModifiers
type DraggableModifier = (
change: { x: number; y: number },
data: {
draggable: Draggable;
drag: DraggableDrag;
item: DraggableDragItem;
phase: 'start' | 'move' | 'end';
},
) => { x: number; y: number };
type positionModifiers = DraggableModifier[];
An array of position modifier functions that should return the position change of a dragged element. Checkout the Draggable Modifiers page for detailed information.
onPrepareStart
type onPrepareStart = (drag: DraggableDrag, draggable: Draggable) => void;
A callback that is called at the end of drag start preparation phase. In this phase the draggable item instances are created and the initial position is computed. All the required DOM reading (for drag start) is also done in this phase.
This callback is called immediately after any preparestart
events that are added via on
method.
onStart
type onStart = (drag: DraggableDrag, draggable: Draggable) => void;
A callback that is called at the end of drag start phase. This phase handles applying the initial positions to the dragged elements, setting up the frozen styles and any other initial setup that require writing to the DOM.
This callback is called immediately after any start
events that are added via on
method.
onPrepareMove
type onPrepareMove = (drag: DraggableDrag, draggable: Draggable) => void;
A callback that is called at the end of drag move preparation phase. This phase handles computing the new position of the dragged elements based on the sensor data.
This callback is called immediately after any preparemove
events that are added via on
method.
onMove
type onMove = (drag: DraggableDrag, draggable: Draggable) => void;
A callback that is called at the end of drag move phase. This phase applies the new positions to the dragged elements.
This callback is called immediately after any move
events that are added via on
method.
onEnd
type onEnd = (drag: DraggableDrag, draggable: Draggable) => void;
A callback that is called at the very end of drag process after all the required cleanup has been done. You will still have access to the drag data when this callback is called, but it will be removed from the draggable instance right after this callback.
This callback is called immediately after any end
events that are added via on
method.
onDestroy
type onDestroy = (draggable: Draggable) => void;
A callback that is called when the draggable is destroyed via destroy
method. This is the last callback that is called before the draggable instance is completely disposed. If there is an active drag when the draggable is destroyed, the onEnd
callback will be called before this callback.
This callback is called immediately after any destroy
events that are added via on
method.
Properties
sensors
type sensors = Sensor[];
An array of all the sensors attached to the Draggable instance. Read-only.
settings
type settings = DraggableSettings;
Current settings
of the Draggable instance. Read-only.
drag
type drag = DraggableDrag | null;
Current drag data or null
if drag is not active.
plugins
type plugins = Record<
string,
{
name: string;
version: string;
}
>;
An object containing all of the Draggable instance's plugins.
isDestroyed
type isDestroyed = boolean;
Is the Draggable instance destroyed or not?
Methods
on
// Type
type on = (
type: 'preparestart' | 'start' | 'preparemove' | 'move' | 'end' | 'destroy',
listener: (e: SensorEvent | null | undefined) => void,
listenerId?: ListenerId,
) => ListenerId;
type ListenerId = null | string | number | symbol | Function | Object;
// Usage
draggable.on('start', (e) => {
console.log('start', e);
});
Adds an event listener to the Draggable instance.
The listener function receives sensor event as it's argument based on the sensor event that instigated this event. For some events the listener might receive null
or undefined
as argument.
The last optional argument is the listener id, which is normally created automatically, but can be provided here manually too.
The method returns a listener id, which can be used to remove this specific listener. By default this will always be a symbol unless manually provided.
off
// Type
type off = (
type: 'preparestart' | 'start' | 'preparemove' | 'move' | 'end' | 'destroy',
listenerId: null | string | number | symbol | Function | Object,
) => void;
// Usage
const id = draggable.on('start', (e) => console.log('start', e));
draggable.off('start', id);
Removes a listener (based on listener id) from an event. The first argument is the event type and the second argument is the listener id.
stop
// Type
type stop = () => void;
// Usage
draggable.stop();
Forcibly stops the draggable's current drag process.
align
// Type
type align = (instant?: boolean) => void;
// Usage: update asynchronously on the next animation frame.
draggable.align();
// Usage: update instantly. May cause extra reflows (jank).
draggable.align(true);
Recomputes the positions of all dragged elements if they have drifted due to e.g. scrolling. This should be called if a dragged element's position goes out of sync inadvertently. Draggable is smart enough to call this automatically when scrolling occurs during dragging, but if you need to realign the elements manually you can call this method.
updateSettings
// Type
type updateSettings = (options?: Partial<DraggableSettings>) => void;
// Usage
draggable.updateSettings({
startPredicate: () => Math.random() > 0.5,
});
Updates the the draggable's settings. Accepts DraggableSettings
as the first argument, only the options you provide will be updated. Note that you only need to provide the options you want to change, the rest will be left as default.
use
// Type
type use = (plugin: (draggable: Draggable) => Draggable) => Draggable;
// Usage
const draggable = new Draggable(
[
// Sensors here...
],
{
// Options here...
},
)
// Plugins here...
.use(myPlugin)
.use(myOtherlugin);
Registers a plugin to the Draggable instance. Returns the Draggable instance so you can chain the method and get updated typings for the instance based on how the plugin(s) extend the Draggable type.
The plugin system is designed to be used so that you register the plugins right away when you instantiate the Draggable. This way you'll get the correct typings to the variable holding the instance. Also, there's no mechanism to unregister a plugin because there really should be no need for that.
Check out the plugin guide to learn how to build custom plugins.
destroy
// Type
type destroy = () => void;
// Usage
draggable.destroy();
Destroy the draggable. Disposes all allocated memory and removes all bound event listeners.