123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393 |
- // drag
- ( function( window, factory ) {
- // universal module definition
- /* jshint strict: false */
- if ( typeof define == 'function' && define.amd ) {
- // AMD
- define( [
- './flickity',
- 'unidragger/unidragger',
- 'fizzy-ui-utils/utils'
- ], function( Flickity, Unidragger, utils ) {
- return factory( window, Flickity, Unidragger, utils );
- });
- } else if ( typeof module == 'object' && module.exports ) {
- // CommonJS
- module.exports = factory(
- window,
- require('./flickity'),
- require('unidragger'),
- require('fizzy-ui-utils')
- );
- } else {
- // browser global
- window.Flickity = factory(
- window,
- window.Flickity,
- window.Unidragger,
- window.fizzyUIUtils
- );
- }
- }( window, function factory( window, Flickity, Unidragger, utils ) {
- 'use strict';
- // ----- defaults ----- //
- utils.extend( Flickity.defaults, {
- draggable: '>1',
- dragThreshold: 3,
- });
- // ----- create ----- //
- Flickity.createMethods.push('_createDrag');
- // -------------------------- drag prototype -------------------------- //
- var proto = Flickity.prototype;
- utils.extend( proto, Unidragger.prototype );
- proto._touchActionValue = 'pan-y';
- // -------------------------- -------------------------- //
- var isTouch = 'createTouch' in document;
- var isTouchmoveScrollCanceled = false;
- proto._createDrag = function() {
- this.on( 'activate', this.onActivateDrag );
- this.on( 'uiChange', this._uiChangeDrag );
- this.on( 'deactivate', this.onDeactivateDrag );
- this.on( 'cellChange', this.updateDraggable );
- // TODO updateDraggable on resize? if groupCells & slides change
- // HACK - add seemingly innocuous handler to fix iOS 10 scroll behavior
- // #457, RubaXa/Sortable#973
- if ( isTouch && !isTouchmoveScrollCanceled ) {
- window.addEventListener( 'touchmove', function() {});
- isTouchmoveScrollCanceled = true;
- }
- };
- proto.onActivateDrag = function() {
- this.handles = [ this.viewport ];
- this.bindHandles();
- this.updateDraggable();
- };
- proto.onDeactivateDrag = function() {
- this.unbindHandles();
- this.element.classList.remove('is-draggable');
- };
- proto.updateDraggable = function() {
- // disable dragging if less than 2 slides. #278
- if ( this.options.draggable == '>1' ) {
- this.isDraggable = this.slides.length > 1;
- } else {
- this.isDraggable = this.options.draggable;
- }
- if ( this.isDraggable ) {
- this.element.classList.add('is-draggable');
- } else {
- this.element.classList.remove('is-draggable');
- }
- };
- // backwards compatibility
- proto.bindDrag = function() {
- this.options.draggable = true;
- this.updateDraggable();
- };
- proto.unbindDrag = function() {
- this.options.draggable = false;
- this.updateDraggable();
- };
- proto._uiChangeDrag = function() {
- delete this.isFreeScrolling;
- };
- // -------------------------- pointer events -------------------------- //
- proto.pointerDown = function( event, pointer ) {
- if ( !this.isDraggable ) {
- this._pointerDownDefault( event, pointer );
- return;
- }
- var isOkay = this.okayPointerDown( event );
- if ( !isOkay ) {
- return;
- }
- this._pointerDownPreventDefault( event );
- this.pointerDownFocus( event );
- // blur
- if ( document.activeElement != this.element ) {
- // do not blur if already focused
- this.pointerDownBlur();
- }
- // stop if it was moving
- this.dragX = this.x;
- this.viewport.classList.add('is-pointer-down');
- // track scrolling
- this.pointerDownScroll = getScrollPosition();
- window.addEventListener( 'scroll', this );
- this._pointerDownDefault( event, pointer );
- };
- // default pointerDown logic, used for staticClick
- proto._pointerDownDefault = function( event, pointer ) {
- // track start event position
- // Safari 9 overrides pageX and pageY. These values needs to be copied. #779
- this.pointerDownPointer = {
- pageX: pointer.pageX,
- pageY: pointer.pageY,
- };
- // bind move and end events
- this._bindPostStartEvents( event );
- this.dispatchEvent( 'pointerDown', event, [ pointer ] );
- };
- var focusNodes = {
- INPUT: true,
- TEXTAREA: true,
- SELECT: true,
- };
- proto.pointerDownFocus = function( event ) {
- var isFocusNode = focusNodes[ event.target.nodeName ];
- if ( !isFocusNode ) {
- this.focus();
- }
- };
- proto._pointerDownPreventDefault = function( event ) {
- var isTouchStart = event.type == 'touchstart';
- var isTouchPointer = event.pointerType == 'touch';
- var isFocusNode = focusNodes[ event.target.nodeName ];
- if ( !isTouchStart && !isTouchPointer && !isFocusNode ) {
- event.preventDefault();
- }
- };
- // ----- move ----- //
- proto.hasDragStarted = function( moveVector ) {
- return Math.abs( moveVector.x ) > this.options.dragThreshold;
- };
- // ----- up ----- //
- proto.pointerUp = function( event, pointer ) {
- delete this.isTouchScrolling;
- this.viewport.classList.remove('is-pointer-down');
- this.dispatchEvent( 'pointerUp', event, [ pointer ] );
- this._dragPointerUp( event, pointer );
- };
- proto.pointerDone = function() {
- window.removeEventListener( 'scroll', this );
- delete this.pointerDownScroll;
- };
- // -------------------------- dragging -------------------------- //
- proto.dragStart = function( event, pointer ) {
- if ( !this.isDraggable ) {
- return;
- }
- this.dragStartPosition = this.x;
- this.startAnimation();
- window.removeEventListener( 'scroll', this );
- this.dispatchEvent( 'dragStart', event, [ pointer ] );
- };
- proto.pointerMove = function( event, pointer ) {
- var moveVector = this._dragPointerMove( event, pointer );
- this.dispatchEvent( 'pointerMove', event, [ pointer, moveVector ] );
- this._dragMove( event, pointer, moveVector );
- };
- proto.dragMove = function( event, pointer, moveVector ) {
- if ( !this.isDraggable ) {
- return;
- }
- event.preventDefault();
- this.previousDragX = this.dragX;
- // reverse if right-to-left
- var direction = this.options.rightToLeft ? -1 : 1;
- if ( this.options.wrapAround ) {
- // wrap around move. #589
- moveVector.x = moveVector.x % this.slideableWidth;
- }
- var dragX = this.dragStartPosition + moveVector.x * direction;
- if ( !this.options.wrapAround && this.slides.length ) {
- // slow drag
- var originBound = Math.max( -this.slides[0].target, this.dragStartPosition );
- dragX = dragX > originBound ? ( dragX + originBound ) * 0.5 : dragX;
- var endBound = Math.min( -this.getLastSlide().target, this.dragStartPosition );
- dragX = dragX < endBound ? ( dragX + endBound ) * 0.5 : dragX;
- }
- this.dragX = dragX;
- this.dragMoveTime = new Date();
- this.dispatchEvent( 'dragMove', event, [ pointer, moveVector ] );
- };
- proto.dragEnd = function( event, pointer ) {
- if ( !this.isDraggable ) {
- return;
- }
- if ( this.options.freeScroll ) {
- this.isFreeScrolling = true;
- }
- // set selectedIndex based on where flick will end up
- var index = this.dragEndRestingSelect();
- if ( this.options.freeScroll && !this.options.wrapAround ) {
- // if free-scroll & not wrap around
- // do not free-scroll if going outside of bounding slides
- // so bounding slides can attract slider, and keep it in bounds
- var restingX = this.getRestingPosition();
- this.isFreeScrolling = -restingX > this.slides[0].target &&
- -restingX < this.getLastSlide().target;
- } else if ( !this.options.freeScroll && index == this.selectedIndex ) {
- // boost selection if selected index has not changed
- index += this.dragEndBoostSelect();
- }
- delete this.previousDragX;
- // apply selection
- // TODO refactor this, selecting here feels weird
- // HACK, set flag so dragging stays in correct direction
- this.isDragSelect = this.options.wrapAround;
- this.select( index );
- delete this.isDragSelect;
- this.dispatchEvent( 'dragEnd', event, [ pointer ] );
- };
- proto.dragEndRestingSelect = function() {
- var restingX = this.getRestingPosition();
- // how far away from selected slide
- var distance = Math.abs( this.getSlideDistance( -restingX, this.selectedIndex ) );
- // get closet resting going up and going down
- var positiveResting = this._getClosestResting( restingX, distance, 1 );
- var negativeResting = this._getClosestResting( restingX, distance, -1 );
- // use closer resting for wrap-around
- var index = positiveResting.distance < negativeResting.distance ?
- positiveResting.index : negativeResting.index;
- return index;
- };
- /**
- * given resting X and distance to selected cell
- * get the distance and index of the closest cell
- * @param {Number} restingX - estimated post-flick resting position
- * @param {Number} distance - distance to selected cell
- * @param {Integer} increment - +1 or -1, going up or down
- * @returns {Object} - { distance: {Number}, index: {Integer} }
- */
- proto._getClosestResting = function( restingX, distance, increment ) {
- var index = this.selectedIndex;
- var minDistance = Infinity;
- var condition = this.options.contain && !this.options.wrapAround ?
- // if contain, keep going if distance is equal to minDistance
- function( d, md ) { return d <= md; } : function( d, md ) { return d < md; };
- while ( condition( distance, minDistance ) ) {
- // measure distance to next cell
- index += increment;
- minDistance = distance;
- distance = this.getSlideDistance( -restingX, index );
- if ( distance === null ) {
- break;
- }
- distance = Math.abs( distance );
- }
- return {
- distance: minDistance,
- // selected was previous index
- index: index - increment
- };
- };
- /**
- * measure distance between x and a slide target
- * @param {Number} x
- * @param {Integer} index - slide index
- */
- proto.getSlideDistance = function( x, index ) {
- var len = this.slides.length;
- // wrap around if at least 2 slides
- var isWrapAround = this.options.wrapAround && len > 1;
- var slideIndex = isWrapAround ? utils.modulo( index, len ) : index;
- var slide = this.slides[ slideIndex ];
- if ( !slide ) {
- return null;
- }
- // add distance for wrap-around slides
- var wrap = isWrapAround ? this.slideableWidth * Math.floor( index / len ) : 0;
- return x - ( slide.target + wrap );
- };
- proto.dragEndBoostSelect = function() {
- // do not boost if no previousDragX or dragMoveTime
- if ( this.previousDragX === undefined || !this.dragMoveTime ||
- // or if drag was held for 100 ms
- new Date() - this.dragMoveTime > 100 ) {
- return 0;
- }
- var distance = this.getSlideDistance( -this.dragX, this.selectedIndex );
- var delta = this.previousDragX - this.dragX;
- if ( distance > 0 && delta > 0 ) {
- // boost to next if moving towards the right, and positive velocity
- return 1;
- } else if ( distance < 0 && delta < 0 ) {
- // boost to previous if moving towards the left, and negative velocity
- return -1;
- }
- return 0;
- };
- // ----- staticClick ----- //
- proto.staticClick = function( event, pointer ) {
- // get clickedCell, if cell was clicked
- var clickedCell = this.getParentCell( event.target );
- var cellElem = clickedCell && clickedCell.element;
- var cellIndex = clickedCell && this.cells.indexOf( clickedCell );
- this.dispatchEvent( 'staticClick', event, [ pointer, cellElem, cellIndex ] );
- };
- // ----- scroll ----- //
- proto.onscroll = function() {
- var scroll = getScrollPosition();
- var scrollMoveX = this.pointerDownScroll.x - scroll.x;
- var scrollMoveY = this.pointerDownScroll.y - scroll.y;
- // cancel click/tap if scroll is too much
- if ( Math.abs( scrollMoveX ) > 3 || Math.abs( scrollMoveY ) > 3 ) {
- this._pointerDone();
- }
- };
- // ----- utils ----- //
- function getScrollPosition() {
- return {
- x: window.pageXOffset,
- y: window.pageYOffset
- };
- }
- // ----- ----- //
- return Flickity;
- }));
|