Last active
April 19, 2017 18:33
-
-
Save amritk/f6685f4b9b14c5e44a93 to your computer and use it in GitHub Desktop.
Trying to get this dragdrop from this codrops article working on the latest version of dragabilly
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* dragdrop.js | |
* http://www.codrops.com | |
* | |
* Licensed under the MIT license. | |
* http://www.opensource.org/licenses/mit-license.php | |
* | |
* Copyright 2014, Codrops | |
* http://www.codrops.com | |
* | |
* From: | |
* http://tympanus.net/codrops/2014/11/11/drag-and-drop-interaction-ideas/ | |
* | |
* Check #todo for code that I just commented for now | |
* | |
*/ | |
;( function() { | |
'use strict'; | |
/*************************************************************/ | |
/******************* Some helper functions *******************/ | |
/*************************************************************/ | |
var body = document.body, docElem = window.document.documentElement, | |
transEndEventNames = { 'WebkitTransition': 'webkitTransitionEnd', 'MozTransition': 'transitionend', 'OTransition': 'oTransitionEnd', 'msTransition': 'MSTransitionEnd', 'transition': 'transitionend' }, | |
transEndEventName = transEndEventNames[ Modernizr.prefixed( 'transition' ) ], | |
support = { transitions : Modernizr.csstransitions }; | |
// https://remysharp.com/2010/07/21/throttling-function-calls | |
function throttle(fn, threshhold, scope) { | |
threshhold || (threshhold = 250); | |
var last, | |
deferTimer; | |
return function () { | |
var context = scope || this; | |
var now = +new Date, | |
args = arguments; | |
if (last && now < last + threshhold) { | |
// hold on to it | |
clearTimeout(deferTimer); | |
deferTimer = setTimeout(function () { | |
last = now; | |
fn.apply(context, args); | |
}, threshhold); | |
} else { | |
last = now; | |
fn.apply(context, args); | |
} | |
}; | |
} | |
// from http://responsejs.com/labs/dimensions/ | |
function getViewportW() { | |
var client = docElem['clientWidth'], inner = window['innerWidth']; | |
return client < inner ? inner : client; | |
} | |
function getViewportH() { | |
var client = docElem['clientHeight'], inner = window['innerHeight']; | |
return client < inner ? inner : client; | |
} | |
function scrollX() { return window.pageXOffset || docElem.scrollLeft; } | |
function scrollY() { return window.pageYOffset || docElem.scrollTop; } | |
// gets the offset of an element relative to the document | |
function getOffset( el ) { | |
var offset = el.getBoundingClientRect(); | |
return { top : offset.top + scrollY(), left : offset.left + scrollX() } | |
} | |
function setTransformStyle( el, tval ) { el.style.transform = tval; } | |
function onEndTransition( el, callback ) { | |
var onEndCallbackFn = function( ev ) { | |
if( support.transitions ) { | |
this.removeEventListener( transEndEventName, onEndCallbackFn ); | |
} | |
if( callback && typeof callback === 'function' ) { callback.call(); } | |
}; | |
if( support.transitions ) { | |
el.addEventListener( transEndEventName, onEndCallbackFn ); | |
} | |
else { | |
onEndCallbackFn(); | |
} | |
} | |
function extend( a, b ) { | |
for( var key in b ) { | |
if( b.hasOwnProperty( key ) ) { | |
a[key] = b[key]; | |
} | |
} | |
return a; | |
} | |
/*************************************************************/ | |
/************************ Drag & Drop ************************/ | |
/*************************************************************/ | |
var is3d = !!getStyleProperty( 'perspective' ) | |
/***************/ | |
/** Droppable **/ | |
/***************/ | |
function Droppable( droppableEl, options ) { | |
this.el = droppableEl; | |
this.options = extend( {}, this.options ); | |
extend( this.options, options ); | |
} | |
Droppable.prototype.options = { | |
onDrop : function(instance, draggableEl) { return false; } | |
} | |
// based on http://stackoverflow.com/a/2752387 : checks if the droppable element is ready to collect the draggable: the draggable element must intersect the droppable in half of its width or height. | |
Droppable.prototype.isDroppable = function( draggableEl ) { | |
var offset1 = getOffset( draggableEl ), width1 = draggableEl.offsetWidth, height1 = draggableEl.offsetHeight, | |
offset2 = getOffset( this.el ), width2 = this.el.offsetWidth, height2 = this.el.offsetHeight; | |
return !(offset2.left > offset1.left + width1 - width1/2 || | |
offset2.left + width2 < offset1.left + width1/2 || | |
offset2.top > offset1.top + height1 - height1/2 || | |
offset2.top + height2 < offset1.top + height1/2 ); | |
} | |
// highlight the droppable if it's ready to collect the draggable | |
Droppable.prototype.highlight = function( draggableEl ) { | |
if( this.isDroppable( draggableEl ) ) | |
classie.add( this.el, 'highlight' ); | |
else | |
classie.remove( this.el, 'highlight' ); | |
} | |
// accepts a draggable element... | |
Droppable.prototype.collect = function( draggableEl ) { | |
// remove highlight class from droppable element | |
classie.remove( this.el, 'highlight' ); | |
this.options.onDrop( this, draggableEl ); | |
} | |
/***************/ | |
/** Draggable **/ | |
/***************/ | |
function Draggable( draggableEl, droppables, options ) { | |
this.el = draggableEl; | |
this.options = extend( {}, this.options ); | |
extend( this.options, options ); | |
this.droppables = droppables; | |
this.scrollableEl = this.options.scrollable === window ? window : document.querySelector( this.options.scrollable ); | |
this.scrollIncrement = 0; | |
if( this.options.helper ) { | |
this.offset = { left : getOffset( this.el ).left, top : getOffset( this.el ).top }; | |
} | |
this.draggie = new Draggabilly( this.el, this.options.draggabilly ); | |
this.initEvents(); | |
} | |
Draggable.prototype.options = { | |
// auto scroll when dragging | |
scroll: false, | |
// element to scroll | |
scrollable : window, | |
// scroll speed (px) | |
scrollSpeed : 20, | |
// minimum distance to the scrollable element edges to trigger the scroll | |
scrollSensitivity : 10, | |
// draggabilly options | |
draggabilly : {}, | |
// if the item should animate back to its original position | |
animBack : true, | |
// clone the draggable and insert it on the same position while dragging the original one | |
helper : false, | |
// callbacks | |
onStart : function() { return false; }, | |
onDrag : function() { return false; }, | |
onEnd : function(wasDropped) { return false; } | |
} | |
Draggable.prototype.initEvents = function() { | |
this.draggie.on( 'dragStart', this.onDragStart.bind(this) ); | |
this.draggie.on( 'dragMove', throttle( this.onDragMove.bind(this), 5 ) ); | |
this.draggie.on( 'dragEnd', this.onDragEnd.bind(this) ); | |
} | |
Draggable.prototype.onDragStart = function( event, pointer ) { | |
//callback | |
this.options.onStart(); | |
// save left & top | |
this.position = { left : 0, top : 0 }; | |
// this.displacement = { left : 0, top : 0 }; | |
// create helper | |
if( this.options.helper ) { | |
this.createHelper( pointer.target ); | |
} | |
// add class is-active to the draggable element (control the draggable z-index) | |
classie.add( pointer.target, 'is-active' ); | |
// highlight droppable elements if draggables intersect | |
this.highlightDroppables(); | |
} | |
Draggable.prototype.onDragMove = function( event, pointer, moveVector ) { | |
//callback | |
this.options.onDrag(); | |
// Keep track of displacement | |
// this.displacement = { left : moveVector.x, top : moveVector.y }; | |
// scroll page if at viewport's edge | |
if( this.options.scroll ) { | |
this.scrollPage( pointer.target ); | |
} | |
// highlight droppable elements if draggables intersect | |
this.highlightDroppables(); | |
} | |
Draggable.prototype.onDragEnd = function( event, pointer ) { | |
// if( this.options.helper ) { | |
// instance.element.style.left = instance.position.x + this.position.left + 'px'; | |
// instance.element.style.top =instance.position.y + this.position.top + 'px'; | |
// } | |
if( this.options.scroll ) { | |
// reset this.scrollIncrement | |
this.scrollIncrement = 0; | |
} | |
// if the draggable && droppable elements intersect then "drop" and move back the draggable | |
var dropped = false; | |
for( var i = 0, len = this.droppables.length; i < len; ++i ) { | |
var droppableEl = this.droppables[i]; | |
if( droppableEl.isDroppable( pointer.target ) ) { | |
dropped = true; | |
droppableEl.collect( pointer.target ); | |
} | |
} | |
//callback | |
this.options.onEnd( dropped ); | |
if( dropped ) { | |
// add class is-dropped to draggable ( controls how the draggable appears again at its original position) | |
classie.add( pointer.target, 'is-dropped' ); | |
// after a timeout remove that class to trigger the transition | |
setTimeout( function() { | |
classie.add( pointer.target, 'is-complete' ); | |
onEndTransition( pointer.target, function() { | |
classie.remove( pointer.target, 'is-complete' ); | |
classie.remove( pointer.target, 'is-dropped' ); | |
} ); | |
}, 25 ); | |
} | |
// move back the draggable element (with or without a transition) | |
this.moveBack( !dropped ); | |
} | |
Draggable.prototype.highlightDroppables = function( el ) { | |
for( var i = 0, len = this.droppables.length; i < len; ++i ) { | |
this.droppables[i].highlight( this.el ); | |
} | |
} | |
Draggable.prototype.createHelper = function() { | |
// clone the original item (same position) | |
var clone = this.el.cloneNode( true ); | |
// because the original element started the dragging, we need to remove the is-dragging class | |
classie.remove( clone, 'is-dragging' ); | |
this.el.parentNode.replaceChild( clone, this.el ); | |
// initialize Draggabilly on the clone.. | |
var draggable = new Draggable( clone, this.droppables, this.options ); | |
// the original item will be absolute on the page - need to set correct position values.. | |
classie.add( this.el, 'helper' ); | |
this.el.style.left = draggable.offset.left + 'px'; | |
this.el.style.top = draggable.offset.top + 'px'; | |
// save new left/top | |
this.position.left = draggable.offset.left; | |
this.position.top = draggable.offset.top; | |
body.appendChild( this.el ); | |
} | |
// move back the draggable to its original position | |
Draggable.prototype.moveBack = function( withAnimation ) { | |
var anim = this.options.animBack && withAnimation; | |
// add animate class (where the transition is defined) | |
if( anim ) { | |
classie.add( this.el, 'animate' ); | |
} | |
// reset translation value | |
setTransformStyle( this.el, is3d ? 'translate3d(0,0,0)' : 'translate(0,0)' ); | |
// apply original left/top | |
this.el.style.left = this.position.left + 'px'; | |
this.el.style.top = this.position.top + 'px'; | |
// remove class animate (transition) and is-active to the draggable element (z-index) | |
var callbackFn = function() { | |
if( anim ) { | |
classie.remove( this.el, 'animate' ); | |
} | |
classie.remove( this.el, 'is-active' ); | |
if( this.options.helper ) { | |
body.removeChild( this.el ); | |
} | |
}.bind( this ); | |
if( anim ) { | |
onEndTransition( this.el, callbackFn ); | |
} | |
else { | |
callbackFn(); | |
} | |
} | |
// check if element is outside of the viewport. TODO: check also for right and left sides | |
Draggable.prototype.outViewport = function() { | |
var scrollSensitivity = Math.abs( this.options.scrollSensitivity ) || 0, | |
scrolled = scrollY(), | |
viewed = scrolled + getViewportH(), | |
elT = getOffset( this.el ).top, | |
elHalfPos = elT + this.el.offsetHeight/2, | |
belowViewport = elT + this.el.offsetHeight + scrollSensitivity > viewed, | |
aboveViewport = elT - scrollSensitivity < scrolled; | |
if( belowViewport ) this.scrolldir = 'down'; | |
else if( aboveViewport ) this.scrolldir = 'up'; | |
return belowViewport || aboveViewport; | |
} | |
// force the scroll on the page when dragging.. | |
Draggable.prototype.scrollPage = function() { | |
// check if draggable is "outside" of the viewport | |
if( this.outViewport( this.el ) ) { | |
this.doScroll(); | |
} | |
else { | |
// reset this.scrollIncrement | |
this.scrollIncrement = 0; | |
} | |
} | |
// Destroy draggabilly | |
Draggable.prototype.destroy = function() { | |
this.draggie.destroy(); | |
} | |
// just considering scroll up and down | |
// this.scrollIncrement is used to accelerate the scrolling. But mainly it's used to avoid the draggable flickering due to the throttle when dragging | |
// todo: scroll right and left | |
// todo: draggabilly multi touch scroll: see https://github.com/desandro/draggabilly/issues/1 | |
Draggable.prototype.doScroll = function() { | |
var speed = this.options.scrollSpeed || 20; | |
this.scrollIncrement++; | |
var val = this.scrollIncrement < speed ? this.scrollIncrement : speed; | |
this.scrollableEl === window ? | |
this.scrollableEl.scrollBy( 0, this.scrolldir === 'up' ? val * -1 : val ) : | |
this.scrollableEl.scrollTop += this.scrolldir === 'up' ? val * -1 : val; | |
} | |
window.Droppable = Droppable; | |
window.Draggable = Draggable; | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment