mirror of
https://github.com/DanielnetoDotCom/YouPHPTube
synced 2025-10-03 17:59:55 +02:00
292 lines
8.9 KiB
JavaScript
292 lines
8.9 KiB
JavaScript
// drag
|
|
( function( window, factory ) {
|
|
// universal module definition
|
|
if ( typeof module == 'object' && module.exports ) {
|
|
// CommonJS
|
|
module.exports = factory(
|
|
window,
|
|
require('./core'),
|
|
require('unidragger'),
|
|
require('fizzy-ui-utils'),
|
|
);
|
|
} else {
|
|
// browser global
|
|
window.Flickity = factory(
|
|
window,
|
|
window.Flickity,
|
|
window.Unidragger,
|
|
window.fizzyUIUtils,
|
|
);
|
|
}
|
|
|
|
}( typeof window != 'undefined' ? window : this,
|
|
function factory( window, Flickity, Unidragger, utils ) {
|
|
|
|
// ----- defaults ----- //
|
|
|
|
Object.assign( Flickity.defaults, {
|
|
draggable: '>1',
|
|
dragThreshold: 3,
|
|
} );
|
|
|
|
// -------------------------- drag prototype -------------------------- //
|
|
|
|
let proto = Flickity.prototype;
|
|
Object.assign( proto, Unidragger.prototype ); // inherit Unidragger
|
|
proto.touchActionValue = '';
|
|
|
|
// -------------------------- -------------------------- //
|
|
|
|
Flickity.create.drag = function() {
|
|
this.on( 'activate', this.onActivateDrag );
|
|
this.on( 'uiChange', this._uiChangeDrag );
|
|
this.on( 'deactivate', this.onDeactivateDrag );
|
|
this.on( 'cellChange', this.updateDraggable );
|
|
this.on( 'pointerDown', this.handlePointerDown );
|
|
this.on( 'pointerUp', this.handlePointerUp );
|
|
this.on( 'pointerDown', this.handlePointerDone );
|
|
this.on( 'dragStart', this.handleDragStart );
|
|
this.on( 'dragMove', this.handleDragMove );
|
|
this.on( 'dragEnd', this.handleDragEnd );
|
|
this.on( 'staticClick', this.handleStaticClick );
|
|
// TODO updateDraggable on resize? if groupCells & slides change
|
|
};
|
|
|
|
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;
|
|
}
|
|
this.element.classList.toggle( 'is-draggable', this.isDraggable );
|
|
};
|
|
|
|
proto._uiChangeDrag = function() {
|
|
delete this.isFreeScrolling;
|
|
};
|
|
|
|
// -------------------------- pointer events -------------------------- //
|
|
|
|
proto.handlePointerDown = function( event ) {
|
|
if ( !this.isDraggable ) {
|
|
// proceed for staticClick
|
|
this.bindActivePointerEvents( event );
|
|
return;
|
|
}
|
|
|
|
let isTouchStart = event.type === 'touchstart';
|
|
let isTouchPointer = event.pointerType === 'touch';
|
|
let isFocusNode = event.target.matches('input, textarea, select');
|
|
if ( !isTouchStart && !isTouchPointer && !isFocusNode ) event.preventDefault();
|
|
if ( !isFocusNode ) this.focus();
|
|
// blur
|
|
if ( document.activeElement !== this.element ) document.activeElement.blur();
|
|
// 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.bindActivePointerEvents( event );
|
|
};
|
|
|
|
// ----- move ----- //
|
|
|
|
proto.hasDragStarted = function( moveVector ) {
|
|
return Math.abs( moveVector.x ) > this.options.dragThreshold;
|
|
};
|
|
|
|
// ----- up ----- //
|
|
|
|
proto.handlePointerUp = function() {
|
|
delete this.isTouchScrolling;
|
|
this.viewport.classList.remove('is-pointer-down');
|
|
};
|
|
|
|
proto.handlePointerDone = function() {
|
|
window.removeEventListener( 'scroll', this );
|
|
delete this.pointerDownScroll;
|
|
};
|
|
|
|
// -------------------------- dragging -------------------------- //
|
|
|
|
proto.handleDragStart = function() {
|
|
if ( !this.isDraggable ) return;
|
|
|
|
this.dragStartPosition = this.x;
|
|
this.startAnimation();
|
|
window.removeEventListener( 'scroll', this );
|
|
};
|
|
|
|
proto.handleDragMove = function( event, pointer, moveVector ) {
|
|
if ( !this.isDraggable ) return;
|
|
|
|
event.preventDefault();
|
|
|
|
this.previousDragX = this.dragX;
|
|
// reverse if right-to-left
|
|
let direction = this.options.rightToLeft ? -1 : 1;
|
|
// wrap around move. #589
|
|
if ( this.isWrapping ) moveVector.x %= this.slideableWidth;
|
|
let dragX = this.dragStartPosition + moveVector.x * direction;
|
|
|
|
if ( !this.isWrapping ) {
|
|
// slow drag
|
|
let originBound = Math.max( -this.slides[0].target, this.dragStartPosition );
|
|
dragX = dragX > originBound ? ( dragX + originBound ) * 0.5 : dragX;
|
|
let endBound = Math.min( -this.getLastSlide().target, this.dragStartPosition );
|
|
dragX = dragX < endBound ? ( dragX + endBound ) * 0.5 : dragX;
|
|
}
|
|
|
|
this.dragX = dragX;
|
|
this.dragMoveTime = new Date();
|
|
};
|
|
|
|
proto.handleDragEnd = function() {
|
|
if ( !this.isDraggable ) return;
|
|
|
|
let { freeScroll } = this.options;
|
|
if ( freeScroll ) this.isFreeScrolling = true;
|
|
// set selectedIndex based on where flick will end up
|
|
let index = this.dragEndRestingSelect();
|
|
|
|
if ( freeScroll && !this.isWrapping ) {
|
|
// 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
|
|
let restingX = this.getRestingPosition();
|
|
this.isFreeScrolling = -restingX > this.slides[0].target &&
|
|
-restingX < this.getLastSlide().target;
|
|
} else if ( !freeScroll && index === this.selectedIndex ) {
|
|
// boost selection if selected index has not changed
|
|
index += this.dragEndBoostSelect();
|
|
}
|
|
delete this.previousDragX;
|
|
// apply selection
|
|
// HACK, set flag so dragging stays in correct direction
|
|
this.isDragSelect = this.isWrapping;
|
|
this.select( index );
|
|
delete this.isDragSelect;
|
|
};
|
|
|
|
proto.dragEndRestingSelect = function() {
|
|
let restingX = this.getRestingPosition();
|
|
// how far away from selected slide
|
|
let distance = Math.abs( this.getSlideDistance( -restingX, this.selectedIndex ) );
|
|
// get closet resting going up and going down
|
|
let positiveResting = this._getClosestResting( restingX, distance, 1 );
|
|
let negativeResting = this._getClosestResting( restingX, distance, -1 );
|
|
// use closer resting for wrap-around
|
|
return positiveResting.distance < negativeResting.distance ?
|
|
positiveResting.index : negativeResting.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 ) {
|
|
let index = this.selectedIndex;
|
|
let minDistance = Infinity;
|
|
let condition = this.options.contain && !this.isWrapping ?
|
|
// if containing, keep going if distance is equal to minDistance
|
|
( dist, minDist ) => dist <= minDist :
|
|
( dist, minDist ) => dist < minDist;
|
|
|
|
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 - horizontal position
|
|
* @param {Integer} index - slide index
|
|
* @returns {Number} - slide distance
|
|
*/
|
|
proto.getSlideDistance = function( x, index ) {
|
|
let len = this.slides.length;
|
|
// wrap around if at least 2 slides
|
|
let isWrapAround = this.options.wrapAround && len > 1;
|
|
let slideIndex = isWrapAround ? utils.modulo( index, len ) : index;
|
|
let slide = this.slides[ slideIndex ];
|
|
if ( !slide ) return null;
|
|
|
|
// add distance for wrap-around slides
|
|
let 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;
|
|
}
|
|
|
|
let distance = this.getSlideDistance( -this.dragX, this.selectedIndex );
|
|
let 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;
|
|
};
|
|
|
|
// ----- scroll ----- //
|
|
|
|
proto.onscroll = function() {
|
|
let scroll = getScrollPosition();
|
|
let scrollMoveX = this.pointerDownScroll.x - scroll.x;
|
|
let 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;
|
|
|
|
} ) );
|