PageRenderTime 55ms CodeModel.GetById 20ms RepoModel.GetById 1ms app.codeStats 0ms

/ajax/libs/draggabilly/1.0.6/draggabilly.js

https://gitlab.com/Mirros/cdnjs
JavaScript | 522 lines | 321 code | 114 blank | 87 comment | 39 complexity | 506819b6269d2a3074ec067fd7204efa MD5 | raw file
  1. /*!
  2. * Draggabilly v1.0.6
  3. * Make that shiz draggable
  4. * http://draggabilly.desandro.com
  5. */
  6. ( function( window ) {
  7. 'use strict';
  8. // vars
  9. var document = window.document;
  10. // -------------------------- helpers -------------------------- //
  11. // extend objects
  12. function extend( a, b ) {
  13. for ( var prop in b ) {
  14. a[ prop ] = b[ prop ];
  15. }
  16. return a;
  17. }
  18. function noop() {}
  19. // ----- get style ----- //
  20. var defView = document.defaultView;
  21. var getStyle = defView && defView.getComputedStyle ?
  22. function( elem ) {
  23. return defView.getComputedStyle( elem, null );
  24. } :
  25. function( elem ) {
  26. return elem.currentStyle;
  27. };
  28. // http://stackoverflow.com/a/384380/182183
  29. var isElement = ( typeof HTMLElement === 'object' ) ?
  30. function isElementDOM2( obj ) {
  31. return obj instanceof HTMLElement;
  32. } :
  33. function isElementQuirky( obj ) {
  34. return obj && typeof obj === 'object' &&
  35. obj.nodeType === 1 && typeof obj.nodeName === 'string';
  36. };
  37. // -------------------------- requestAnimationFrame -------------------------- //
  38. // https://gist.github.com/1866474
  39. var lastTime = 0;
  40. var prefixes = 'webkit moz ms o'.split(' ');
  41. // get unprefixed rAF and cAF, if present
  42. var requestAnimationFrame = window.requestAnimationFrame;
  43. var cancelAnimationFrame = window.cancelAnimationFrame;
  44. // loop through vendor prefixes and get prefixed rAF and cAF
  45. var prefix;
  46. for( var i = 0; i < prefixes.length; i++ ) {
  47. if ( requestAnimationFrame && cancelAnimationFrame ) {
  48. break;
  49. }
  50. prefix = prefixes[i];
  51. requestAnimationFrame = requestAnimationFrame || window[ prefix + 'RequestAnimationFrame' ];
  52. cancelAnimationFrame = cancelAnimationFrame || window[ prefix + 'CancelAnimationFrame' ] ||
  53. window[ prefix + 'CancelRequestAnimationFrame' ];
  54. }
  55. // fallback to setTimeout and clearTimeout if either request/cancel is not supported
  56. if ( !requestAnimationFrame || !cancelAnimationFrame ) {
  57. requestAnimationFrame = function( callback ) {
  58. var currTime = new Date().getTime();
  59. var timeToCall = Math.max( 0, 16 - ( currTime - lastTime ) );
  60. var id = window.setTimeout( function() {
  61. callback( currTime + timeToCall );
  62. }, timeToCall );
  63. lastTime = currTime + timeToCall;
  64. return id;
  65. };
  66. cancelAnimationFrame = function( id ) {
  67. window.clearTimeout( id );
  68. };
  69. }
  70. // -------------------------- definition -------------------------- //
  71. function draggabillyDefinition( classie, EventEmitter, eventie, getStyleProperty, getSize ) {
  72. // -------------------------- support -------------------------- //
  73. var transformProperty = getStyleProperty('transform');
  74. // TODO fix quick & dirty check for 3D support
  75. var is3d = !!getStyleProperty('perspective');
  76. // -------------------------- -------------------------- //
  77. function Draggabilly( element, options ) {
  78. this.element = element;
  79. this.options = extend( {}, this.options );
  80. extend( this.options, options );
  81. this._create();
  82. }
  83. // inherit EventEmitter methods
  84. extend( Draggabilly.prototype, EventEmitter.prototype );
  85. Draggabilly.prototype.options = {
  86. };
  87. Draggabilly.prototype._create = function() {
  88. // properties
  89. this.position = {};
  90. this._getPosition();
  91. this.startPoint = { x: 0, y: 0 };
  92. this.dragPoint = { x: 0, y: 0 };
  93. this.startPosition = extend( {}, this.position );
  94. // set relative positioning
  95. var style = getStyle( this.element );
  96. if ( style.position !== 'relative' && style.position !== 'absolute' ) {
  97. this.element.style.position = 'relative';
  98. }
  99. this.enable();
  100. this.setHandles();
  101. };
  102. /**
  103. * set this.handles and bind start events to 'em
  104. */
  105. Draggabilly.prototype.setHandles = function() {
  106. this.handles = this.options.handle ?
  107. this.element.querySelectorAll( this.options.handle ) : [ this.element ];
  108. for ( var i=0, len = this.handles.length; i < len; i++ ) {
  109. var handle = this.handles[i];
  110. // bind pointer start event
  111. // listen for both, for devices like Chrome Pixel
  112. // which has touch and mouse events
  113. eventie.bind( handle, 'mousedown', this );
  114. eventie.bind( handle, 'touchstart', this );
  115. disableImgOndragstart( handle );
  116. }
  117. };
  118. // remove default dragging interaction on all images in IE8
  119. // IE8 does its own drag thing on images, which messes stuff up
  120. function noDragStart() {
  121. return false;
  122. }
  123. // TODO replace this with a IE8 test
  124. var isIE8 = 'attachEvent' in document.documentElement;
  125. // IE8 only
  126. var disableImgOndragstart = !isIE8 ? noop : function( handle ) {
  127. if ( handle.nodeName === 'IMG' ) {
  128. handle.ondragstart = noDragStart;
  129. }
  130. var images = handle.querySelectorAll('img');
  131. for ( var i=0, len = images.length; i < len; i++ ) {
  132. var img = images[i];
  133. img.ondragstart = noDragStart;
  134. }
  135. };
  136. // get left/top position from style
  137. Draggabilly.prototype._getPosition = function() {
  138. // properties
  139. var style = getStyle( this.element );
  140. var x = parseInt( style.left, 10 );
  141. var y = parseInt( style.top, 10 );
  142. // clean up 'auto' or other non-integer values
  143. this.position.x = isNaN( x ) ? 0 : x;
  144. this.position.y = isNaN( y ) ? 0 : y;
  145. this._addTransformPosition( style );
  146. };
  147. // add transform: translate( x, y ) to position
  148. Draggabilly.prototype._addTransformPosition = function( style ) {
  149. if ( !transformProperty ) {
  150. return;
  151. }
  152. var transform = style[ transformProperty ];
  153. // bail out if value is 'none'
  154. if ( transform.indexOf('matrix') !== 0 ) {
  155. return;
  156. }
  157. // split matrix(1, 0, 0, 1, x, y)
  158. var matrixValues = transform.split(',');
  159. // translate X value is in 12th or 4th position
  160. var xIndex = transform.indexOf('matrix3d') === 0 ? 12 : 4;
  161. var translateX = parseInt( matrixValues[ xIndex ], 10 );
  162. // translate Y value is in 13th or 5th position
  163. var translateY = parseInt( matrixValues[ xIndex + 1 ], 10 );
  164. this.position.x += translateX;
  165. this.position.y += translateY;
  166. };
  167. // -------------------------- events -------------------------- //
  168. // trigger handler methods for events
  169. Draggabilly.prototype.handleEvent = function( event ) {
  170. var method = 'on' + event.type;
  171. if ( this[ method ] ) {
  172. this[ method ]( event );
  173. }
  174. };
  175. // returns the touch that we're keeping track of
  176. Draggabilly.prototype.getTouch = function( touches ) {
  177. for ( var i=0, len = touches.length; i < len; i++ ) {
  178. var touch = touches[i];
  179. if ( touch.identifier === this.pointerIdentifier ) {
  180. return touch;
  181. }
  182. }
  183. };
  184. // ----- start event ----- //
  185. Draggabilly.prototype.onmousedown = function( event ) {
  186. this.dragStart( event, event );
  187. };
  188. Draggabilly.prototype.ontouchstart = function( event ) {
  189. // disregard additional touches
  190. if ( this.isDragging ) {
  191. return;
  192. }
  193. this.dragStart( event, event.changedTouches[0] );
  194. };
  195. function setPointerPoint( point, pointer ) {
  196. point.x = pointer.pageX !== undefined ? pointer.pageX : pointer.clientX;
  197. point.y = pointer.pageY !== undefined ? pointer.pageY : pointer.clientY;
  198. }
  199. /**
  200. * drag start
  201. * @param {Event} event
  202. * @param {Event or Touch} pointer
  203. */
  204. Draggabilly.prototype.dragStart = function( event, pointer ) {
  205. if ( !this.isEnabled ) {
  206. return;
  207. }
  208. if ( event.preventDefault ) {
  209. event.preventDefault();
  210. } else {
  211. event.returnValue = false;
  212. }
  213. var isTouch = event.type === 'touchstart';
  214. // save pointer identifier to match up touch events
  215. this.pointerIdentifier = pointer.identifier;
  216. this._getPosition();
  217. this.measureContainment();
  218. // point where drag began
  219. setPointerPoint( this.startPoint, pointer );
  220. // position _when_ drag began
  221. this.startPosition.x = this.position.x;
  222. this.startPosition.y = this.position.y;
  223. // reset left/top style
  224. this.setLeftTop();
  225. this.dragPoint.x = 0;
  226. this.dragPoint.y = 0;
  227. // bind move and end events
  228. this._bindEvents({
  229. events: isTouch ? [ 'touchmove', 'touchend', 'touchcancel' ] :
  230. [ 'mousemove', 'mouseup' ],
  231. // IE8 needs to be bound to document
  232. node: event.preventDefault ? window : document
  233. });
  234. classie.add( this.element, 'is-dragging' );
  235. // reset isDragging flag
  236. this.isDragging = true;
  237. this.emitEvent( 'dragStart', [ this, event, pointer ] );
  238. // start animation
  239. this.animate();
  240. };
  241. Draggabilly.prototype._bindEvents = function( args ) {
  242. for ( var i=0, len = args.events.length; i < len; i++ ) {
  243. var event = args.events[i];
  244. eventie.bind( args.node, event, this );
  245. }
  246. // save these arguments
  247. this._boundEvents = args;
  248. };
  249. Draggabilly.prototype._unbindEvents = function() {
  250. var args = this._boundEvents;
  251. // IE8 can trigger dragEnd twice, check for _boundEvents
  252. if ( !args || !args.events ) {
  253. return;
  254. }
  255. for ( var i=0, len = args.events.length; i < len; i++ ) {
  256. var event = args.events[i];
  257. eventie.unbind( args.node, event, this );
  258. }
  259. delete this._boundEvents;
  260. };
  261. Draggabilly.prototype.measureContainment = function() {
  262. var containment = this.options.containment;
  263. if ( !containment ) {
  264. return;
  265. }
  266. this.size = getSize( this.element );
  267. var elemRect = this.element.getBoundingClientRect();
  268. // use element if element
  269. var container = isElement( containment ) ? containment :
  270. // fallback to querySelector if string
  271. typeof containment === 'string' ? document.querySelector( containment ) :
  272. // otherwise just `true`, use the parent
  273. this.element.parentNode;
  274. this.containerSize = getSize( container );
  275. var containerRect = container.getBoundingClientRect();
  276. this.relativeStartPosition = {
  277. x: elemRect.left - containerRect.left,
  278. y: elemRect.top - containerRect.top
  279. };
  280. };
  281. // ----- move event ----- //
  282. Draggabilly.prototype.onmousemove = function( event ) {
  283. this.dragMove( event, event );
  284. };
  285. Draggabilly.prototype.ontouchmove = function( event ) {
  286. var touch = this.getTouch( event.changedTouches );
  287. if ( touch ) {
  288. this.dragMove( event, touch );
  289. }
  290. };
  291. /**
  292. * drag move
  293. * @param {Event} event
  294. * @param {Event or Touch} pointer
  295. */
  296. Draggabilly.prototype.dragMove = function( event, pointer ) {
  297. setPointerPoint( this.dragPoint, pointer );
  298. this.dragPoint.x -= this.startPoint.x;
  299. this.dragPoint.y -= this.startPoint.y;
  300. if ( this.options.containment ) {
  301. var relX = this.relativeStartPosition.x;
  302. var relY = this.relativeStartPosition.y;
  303. this.dragPoint.x = Math.max( this.dragPoint.x, -relX );
  304. this.dragPoint.y = Math.max( this.dragPoint.y, -relY );
  305. this.dragPoint.x = Math.min( this.dragPoint.x, this.containerSize.width - relX - this.size.width );
  306. this.dragPoint.y = Math.min( this.dragPoint.y, this.containerSize.height - relY - this.size.height );
  307. }
  308. this.position.x = this.startPosition.x + this.dragPoint.x;
  309. this.position.y = this.startPosition.y + this.dragPoint.y;
  310. this.emitEvent( 'dragMove', [ this, event, pointer ] );
  311. };
  312. // ----- end event ----- //
  313. Draggabilly.prototype.onmouseup = function( event ) {
  314. this.dragEnd( event, event );
  315. };
  316. Draggabilly.prototype.ontouchend = function( event ) {
  317. var touch = this.getTouch( event.changedTouches );
  318. if ( touch ) {
  319. this.dragEnd( event, touch );
  320. }
  321. };
  322. /**
  323. * drag end
  324. * @param {Event} event
  325. * @param {Event or Touch} pointer
  326. */
  327. Draggabilly.prototype.dragEnd = function( event, pointer ) {
  328. this.isDragging = false;
  329. delete this.pointerIdentifier;
  330. // use top left position when complete
  331. if ( transformProperty ) {
  332. this.element.style[ transformProperty ] = '';
  333. this.setLeftTop();
  334. }
  335. // remove events
  336. this._unbindEvents();
  337. classie.remove( this.element, 'is-dragging' );
  338. this.emitEvent( 'dragEnd', [ this, event, pointer ] );
  339. };
  340. // ----- cancel event ----- //
  341. // coerce to end event
  342. Draggabilly.prototype.ontouchcancel = function( event ) {
  343. var touch = this.getTouch( event.changedTouches );
  344. this.dragEnd( event, touch );
  345. };
  346. // -------------------------- animation -------------------------- //
  347. Draggabilly.prototype.animate = function() {
  348. // only render and animate if dragging
  349. if ( !this.isDragging ) {
  350. return;
  351. }
  352. this.positionDrag();
  353. var _this = this;
  354. requestAnimationFrame( function animateFrame() {
  355. _this.animate();
  356. });
  357. };
  358. // transform translate function
  359. var translate = is3d ?
  360. function( x, y ) {
  361. return 'translate3d( ' + x + 'px, ' + y + 'px, 0)';
  362. } :
  363. function( x, y ) {
  364. return 'translate( ' + x + 'px, ' + y + 'px)';
  365. };
  366. // left/top positioning
  367. Draggabilly.prototype.setLeftTop = function() {
  368. this.element.style.left = this.position.x + 'px';
  369. this.element.style.top = this.position.y + 'px';
  370. };
  371. Draggabilly.prototype.positionDrag = transformProperty ?
  372. function() {
  373. // position with transform
  374. this.element.style[ transformProperty ] = translate( this.dragPoint.x, this.dragPoint.y );
  375. } : Draggabilly.prototype.setLeftTop;
  376. Draggabilly.prototype.enable = function() {
  377. this.isEnabled = true;
  378. };
  379. Draggabilly.prototype.disable = function() {
  380. this.isEnabled = false;
  381. if ( this.isDragging ) {
  382. this.dragEnd();
  383. }
  384. };
  385. return Draggabilly;
  386. } // end definition
  387. // -------------------------- transport -------------------------- //
  388. if ( typeof define === 'function' && define.amd ) {
  389. // AMD
  390. define( [
  391. 'classie/classie',
  392. 'eventEmitter/EventEmitter',
  393. 'eventie/eventie',
  394. 'get-style-property/get-style-property',
  395. 'get-size/get-size'
  396. ],
  397. draggabillyDefinition );
  398. } else {
  399. // browser global
  400. window.Draggabilly = draggabillyDefinition(
  401. window.classie,
  402. window.EventEmitter,
  403. window.eventie,
  404. window.getStyleProperty,
  405. window.getSize
  406. );
  407. }
  408. })( window );