Jump to content
GreenSock

Draggable

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 like bounds:{top:100, left:0, width:1000, height:800} or specific maximum/minimum values like bounds:{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 the type ("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 call TweenLite.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, and onClick. 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, or autoScroll: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, like minimumMovement: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 a snap 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 the clickableTest config property to a function of your choosing that returns true or false.
  • Draggable can be used without ThrowPropsPlugin, 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 their position 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 and this.y inside those functions. And if you applied bounds, you can also get the maximum and minimum "legal" values for that particular instance using this.maxX, this.minX, this.maxY, and this.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 set throwProps:true in the vars config object, like Draggable.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.

Constructor

Draggable( target:Object, vars:Object ) ;

Contstructor

Properties

autoScroll : Number

To enable auto-scrolling when a Draggable is dragged within 40px of an edge of a scrollable container, set autoScroll to a non-zero value, where 1 is normal speed, 2 is double-speed, etc. (you can use any number). For a more intuitive/natural feel, it will scroll faster as the mouse/touch gets closer to the edge. The default value is 0 (no auto-scrolling).

deltaX : Number

The change in the x-related value since the last drag event. 

deltaY : Number

The change in the y-related value since the last drag event. 

endRotation : Number

[read-only] [only applies to type:"rotation"] The ending rotation of the Draggable instance which is calculated as soon as the mouse/touch is released after a drag, meaning you can use it to predict precisely where it'll land after a throwProps flick.

endX : Number

[read-only] The ending x (horizontal) position of the Draggable instance which is calculated as soon as the mouse/touch is released after a drag, meaning you can use it to predict precisely where it'll land after a throwProps flick.

endY : Number

[read-only] The ending y (vertical) position of the Draggable instance which is calculated as soon as the mouse/touch is released after a drag, meaning you can use it to predict precisely where it'll land after a throwProps flick.

isThrowing : Boolean

Reports if the target of a Draggable is being thrown using a ThrowPropsPlugin tween.

lockAxis : Boolean

If true, dragging more than 2 pixels in either direction (horizontally or vertically) will lock movement into that axis so that the element can only be dragged that direction (horizontally or vertically, whichever had the most initial movement).

lockedAxis : String

[read-only] The axis along which movement is locked during that particular drag (either "x" or "y"). For example, if lockAxis is true on a Draggable of type:"x,y", and the user starts dragging horizontally, lockedAxis would be "y" because vertical movement won't be allowed during that drag.

maxRotation : Number

[read-only] [only applies to type:"rotation"] When bounds are applied, maxRotation refers to the maximum "legal" rotation.

maxX : Number

[read-only] When bounds are applied, maxX refers to the maximum "legal" value of the horizontal property (either "x" or "left", depending on which type the Draggable is).

maxY : Number

[read-only] When bounds are applied, maxY refers to the maximum "legal" value of the vertical property (either "y" or "top", depending on which type the Draggable is).

minRotation : Number

[read-only] [only applies to type:"rotation"] When bounds are applied, minRotation refers to the minimum "legal" rotation.

minX : Number

[read-only] When bounds are applied, minX refers to the minimum "legal" value of the horizontal property (either "x" or "left", depending on which type the Draggable is).

minY : Number

[read-only] When bounds are applied, minY refers to the minimum "legal" value of the vertical property (either "y" or "top", depending on which type the Draggable is).

pointerEvent : Object

[read-only] The last pointer event (either a mouse event or touch event) that affected the Draggable instance.

pointerX : Number

[read-only] The x (horizontal) position of the pointer (mouse or touch) associated with the Draggable's last event (like event.pageX).

pointerY : Number

[read-only] The y (vertical) position of the pointer (mouse or touch) associated with the Draggable's last event (like event.pageY).

rotation : Number

[read-only] [only applies to type:"rotation"] The current rotation (in degrees) of the Draggable instance.

scrollProxy : Object

[read-only] A special object that gets created for type:"scroll" (or "scrollTop" or "scrollLeft") Draggables; this object manages the scrolling behavior, applying the necessary transforms or margins to accomplish overscrolling when necessary.

target : Object

The object that is being dragged.

tween : TweenLite

[read-only] The TweenLite instance that gets created as soon as the mouse (or touch) is released (when throwProps is true) - this allows you to check its duration or pause/resume or change its timeScale or whatever you want.

vars : Object

The vars object passed into the constructor which stores configuration variables like type, bounds, onPress, onDrag, etc.

x : Number

[read-only] The current x (horizontal) position of the Draggable instance.

y : Number

[read-only] The current y (vertical) position of the Draggable instance.

zIndex : Number = 1000

[static] The starting zIndex that gets applied by default when an element is pressed/touched (for positional types, like "x,y", "top,left", etc.

Methods

addEventListener( event:String, listener:Function ) : void

Registers a function that should be called each time a particular type of event occurs, like "drag" or "dragEnd".

applyBounds( newBounds:Object ) : Draggable

Immediately updates and applies bounds, ensuring that the target element is within the bounds (if any were defined).

create( target:Object, vars:Object ) : Array

[static] Provides a more flexible way to create Draggable instances than the constructor (new Draggable(...)) because the Draggable.create() method can accommodate multiple elements (either as an array of elements or a jQuery object with many results) or even selector text like ".yourClass" which gets fed to whatever TweenLite.selector is (defaults to jQuery if it's loaded).

disable( ) : Draggable

Disables the Draggable instance so that it cannot be dragged anymore (unless enable() is called).

enable( ) : Draggable

Enables the Draggable instance.

enabled( value:Boolean ) : Boolean

Gets or sets the enabled state.

endDrag( event:Object ) : void

You may force the Draggable to immediately stop interactively dragging by calling endDrag() and passing it the original mouse or touch event that initiated the stop - this is necessary because Draggable must inspect that event for various information like pageX, pageY, target, etc.

get( target:Object ) : Draggable

[static] Provides an easy way to get the Draggable instance that's associated with a particular DOM element.

getDirection( from:String | Element ) : String

Returns the direction (right | left | up | down | left-up | left-down | right-up | right-down) as measured from either where the drag started (the default) or the moment-by-moment velocity, or its proximity to another element that you define.

hitTest( testObject:Object, threshold:* ) : Boolean

Provides an easy way to test whether or not the target element overlaps with a particular element (or the mouse position) according to whatever threshold you [optionally] define.

hitTest( testObject:Object, threshold:* ) : Boolean

Provides an easy way to test whether or not the target element overlaps with a particular element (or the mouse position) according to whatever threshold you [optionally] define.

kill( ) : Draggable

Disables the Draggable instance and removes it from the internal lookup table so that it is made eligible for garbage collection and it cannot be dragged anymore (unless enable() is called).

startDrag( event:Object ) : void

This is rarely used, but you may force the Draggable to begin dragging by calling startDrag() and passing it the original mouse/touch/pointer event that initiated things - this is necessary because Draggable must inspect that event for various information like pageX, pageY, target, etc.

timeSinceDrag( ) : Number

Returns the time (in seconds) that has elapsed since the last drag ended - this can be useful in situations where you want to skip certain actions if a drag just occurred.

update( applyBounds:Boolean, sticky:Boolean ) : Draggable

Updates the Draggable's x/y properties to reflect the target element's current position.

Copyright 2017, GreenSock. All rights reserved. This work is subject to theterms of useor for Club GreenSock members, the software agreement that was issued with the membership.
×