Provides a surprisingly simple way to make virtually any DOM element draggable, spinnable, tossable, and even flick-scrollable using mouse and/or touch events, plus Draggable
integrates beautifully (and optionally) withThrowPropsPlugin
so that the user can flick and have the motion decelerate smoothly based on momentum.
For an interactive demo, go to http://greensock.com/draggable/.
Features
- Touch enabled - works great on tablets, phones, and desktop browsers.
- Incredibly smooth - GPU-accelerated and requestAnimationFrame-driven for ultimate performance. Compared to other options out there,
Draggable
just feels far more natural and fluid, particularly when imposing bounds and momentum. - Momentum-based animation - if you have ThrowPropsPlugin loaded, you can simply set
throwProps:true
in the config object and it'll automatically apply natural, momentum-based movement after the mouse/touch is released, causing the object to glide gracefully to a stop. You can even control the amount of resistance, maximum or minimum duration, etc. - Impose bounds - tell a draggable element to stay within the bounds of another DOM element (a container) as in
bounds:"#container"
or define bounds as coordinates likebounds:{top:100, left:0, width:1000, height:800}
or specific maximum/minimum values likebounds:{minRotation:0, maxRotation:270}
. - Sense overlaps with hitTest() - see if one element is overlapping another and even set a tolerance threshold (like at least 20 pixels or 25% of either element's total surface area) using the super-flexible
Draggable.hitTest()
method. Feed it a mouse event and it'll tell you if the mouse is over the element. See http://codepen.io/GreenSock/pen/GFBvn for a simple example. - Define a trigger element - maybe you want only a certain area to trigger the dragging (like the top bar of a window) - it's as simple as
trigger:"#topBar"
, for example. - Drag position, rotation, or scroll - lots of drag types to choose from:
"x,y"
|"top,left"
|"rotation"
|"scroll"
|"x"
|"y"
|"top"
|"left"
|"scrollTop"
|"scrollLeft"
- Lock movement along a certain axis - set
lockAxis:true
and Draggable will watch the direction the user starts to drag and then restrict it to that axis. Or if you only want to allow vertical or horizontal movement, that's easy too using thetype
("top"
,"y"
or"scrollTop"
only allow vertical movement;"x"
,"left"
, or"scrollLeft"
only allow horizontal movement). - Rotation honors transform origin - by default, spinnable elements will rotate around their center, but you can set
transformOrigin
to something else to make the pivot point be elsewhere. For example, if you callTweenLite.set(yourElement, {transformOrigin:"top left"})
before dragging, it will rotate around its top left corner. Or use % or px. Whatever is set in the element's css will be honored. - Rich callback system and event dispatching - you can use any of the following callbacks:
onPress
,onDragStart
,onDrag
,onDragEnd
,onRelease,
,onLockAxis
, andonClick
. Inside the callbacks,"this"
refers to the Draggable instance itself, so you can easily access its "target" or bounds, etc. If you prefer event listeners instead, Draggable dispatches events too so you can do things likeyourDraggable.addEventListener("dragend", yourFunc);
- Works great with SVG
- Even works in transformed containers! Got a Draggable inside a rotated/scaled container? No problem. No other tool handles this properly that we've seen.
- Auto-scrolling, even in multiple containers - set
autoScroll:1
for normal-speed auto scrolling, orautoScroll:2
would scroll twice as fast, etc. The closer you move toward the edge, the faster scrolling gets. See a demo here (added in version 0.12.0) - Sense clicks when the element moves less than 3 pixels - a common challenge is figuring out when a user is trying to click/tap an object rather than drag it, so if the mouse/touch moves less than 3 pixels from its starting position, it will be interpreted as a "click" and the onClick callback will be called (and a "click" event dispatched) without actually moving the element. You can define a different threshold using
minimumMovement
config property, likeminimumMovement:6
for 6 pixels. - Even works in IE8! - all major browsers are supported.
Usage
In its simplest form, you can make an element draggable (vertically and horizontally) like this:
Draggable.create("#yourID");
This will simply find the element with the ID "yourID"
and make it draggable with no bounds or any kinetic motion after release. You don't need to use selector text either - you can pass the element itself or a jQuery object.
Use the vars
parameter to define various other configuration options. For example, to make the object scroll only vertically using the "y"
transform and stay within the bounds of a DOM element with an ID of"container"
, and call a function when clicked and another when the drag ends and make it have momentum-based motion (assuming you loaded ThrowPropsPlugin), do this:
Draggable.create("#yourID", { type:"y", bounds: document.getElementById("container"), throwProps:true, onClick:function() { console.log("clicked"); }, onDragEnd:function() { console.log("drag ended"); } });
Or to make something spinnable (dragging rotates the element), you could simply do:
Draggable.create("#yourID", { type:"rotation", throwProps:true });
And to add the ability to snap to 90-degree increments after the mouse/touch is released (like flick-spinning that always lands on 90-degree increments), use the snap
option:
Draggable.create("#yourID", { type:"rotation", throwProps:true, snap:function(value) { //this function gets called by ThrowPropsPlugin when the mouse/finger is released and it plots where rotation should normally end and we can alter that value and return a new one instead. This gives us an easy way to apply custom snapping behavior with any logic we want. In this case, we'll just make sure the end value snaps to 90-degree increments but only when the "snap" checkbox is selected. return Math.round(value / 90) * 90; } });
Or to make the element flick-scrollable, so that dragging it actually scrolls the content, make sure you've set the element's height (and/or width), and then do this:
Draggable.create("#yourID", { type:"scroll", throwProps:true });
Config object properties
Snapping
Draggable has advanced snapping capabilities. You can define asnap
value in the config object to control where the Draggable will snap AFTER it is released, or you can define a liveSnap
value where the Draggable should snap WHILE dragging. You can define these values in any of the following ways:
As an array of snap-to values
Draggable.create("#id", { type:"x,y", liveSnap:{ //snaps to the closest point in the array, but only when it's within 15px (new in GSAP 1.20.0 release): points:[{x:0, y:0}, {x:100, y:0}, {x:200, y:50}], radius: 15 } });
points
is a special property introduced in GSAP 1.20.0 that allows you to combine both x and y logic into a single place. You can also use separate per-property arrays:
Draggable.create("#id", { type:"x,y", liveSnap:{ //x and y (or top and left) can each have their own array of values to snap to: x:[0, 100, 200, 300], y:[0, 50, 100, 150] } });
As a function with custom logic
Draggable.create("#id", { type:"x,y", liveSnap:{ points: function(point) { //if it's within 100px, snap exactly to 500,250 var dx = point.x - 500; var dy = point.y - 250; if (Math.sqrt(dx * dx + dy * dy) < 100) { return {x:500, y:250}; } return point; //otherwise don't change anything. } } });
Or use separate per-property functions:
Draggable.create("#id", { type:"x,y", liveSnap:{ x: function(value) { //snap to the closest increment of 50. return Math.round(value / 50) * 50; }, y: function(value) { //snap to the closest increment of 25. return Math.round(value / 25) * 25; } } });
It's just as simple for a rotation Draggable:
Draggable.create("#id", { type:"rotation", liveSnap:{ rotation: function(value) { //snap to the closest increment of 10. return Math.round(value / 10) * 10; } } });
Getting the velocity
As long as you've loaded ThrowPropsPlugin
and set throwProps:true
on your Draggable, you can tap into the ThrowPropsPlugin.getVelocity()
method. Draggable will automatically start tracking the velocity of the necessary properties based on whatever its "type" is (type:"x,y"
will track x and y, type:"rotation"
will track rotation, etc.). The only odd duck is "scroll" (or "scrollTop" or "scrollLeft") because browsers don't allow overscrolling, so Draggable has to create a special ScrollProxy
object that manages the complexity of adding transforms or margins when necessary. Therefore, to get the scrolling-based velocity, you'd use that proxy and check its "top" or "left" values ("top" for the vertical scrolling velocity, and "left" for horizontal). Here are a few examples that you could add to the onDragEnd
callback to log the velocity when the user releases their mouse/touch:
//positional velocity Draggable.create("#movableID", { type:"x,y", throwProps:true, onDragEnd:function() { console.log("x velocity is: " + ThrowPropsPlugin.getVelocity(this.target, "x") + " and the duration is " + this.tween.duration() + " seconds."); } }); //scroll velocity Draggable.create("#scrollableID", { type:"scroll", throwProps:true, onDragEnd:function() { console.log("vertical scroll velocity is: " + ThrowPropsPlugin.getVelocity(this.scrollProxy, "top") + ", horizontal scroll velocity is: " + ThrowPropsPlugin.getVelocity(this.scrollProxy, "left")); } });
Notes, dependencies, and limitations
- Requires TweenLite and CSSPlugin (or just TweenMax which already contains both TweenLite and CSSPlugin). Does not require jQuery or any other framework.
- If you want a particular element to be "clickable", thus ignored by Draggable, simply add a
data-clickable="true"
attribute to it, or an onclick. By default, Draggable automatically ignores clicks on<a>
,<input>
,<select>
,<button>
, and<textarea>
elements. If you prefer to run your own logic to determine if an object should be considered "clickable", you can set theclickableTest
config property to a function of your choosing that returns true or false. Draggable
can be used withoutThrowPropsPlugin
, but doing so will disable any momentum-based motion (like being able to flick objects and have them continue while decelerating).ThrowPropsPlugin
is a membership benefit of Club GreenSock. Please consider joining if you haven't already.- In order to make things moveable via their
"top"
and"left"
css properties, you must make sure that the elements have theirposition
css property set to either"relative"
or"absolute"
(that's just how css works). - By default, all callback functions and "snap" functions and "liveSnap" functions are scoped to the associated Draggable instance, so "this" refers to the Draggable instance. You can get the current horizontal or vertical values using
this.x
andthis.y
inside those functions. And if you applied bounds, you can also get the maximum and minimum "legal" values for that particular instance usingthis.maxX, this.minX, this.maxY
, andthis.minY
. - Having trouble with momentum-based motion? Make sure you have
ThrowPropsPlugin
loaded. It's not in the public downloads because it is a membership benefit of Club GreenSock; you get it by logging into your GreenSock account and downloading it there. Also, make sure you've setthrowProps:true
in thevars
config object, likeDraggable.create(yourObject, {throwProps:true});
- If you use an element for the
bounds
, it should not be rotated differently than the target element.
Video of Draggable in action
Examples
For an interactive demo, go to http://greensock.com/draggable/.
The following example creates a green box and a red box that you can drag and toss around the screen in a natural, fluid way. If you check the "Snap to grid" checkbox, the boxes will always land exactly on the grid.