UNPKG

slider-pro-js

Version:

Modular JavaScript slider that is dependency-free, rich-featured, flexible and easy to use

357 lines (263 loc) 13.7 kB
import { getParent } from '../../helpers/util.js'; class ThumbnailTouchSwipe { // The namespace to be used when adding event listeners namespace = 'thumbnailtouchswipe'; // Reference to the base slider instance slider; // Reference to the base thumbnails instance thumbnails; // Stores the current settings of the slider settings; // Indicates whether the touch swipe is enabled for the thumbnails isTouchSwipeEnabled = false; // The x and y coordinates of the pointer/finger's starting position touchStartPoint = { x: 0, y: 0 }; // The x and y coordinates of the pointer/finger's end position touchEndPoint = { x: 0, y: 0 }; // The distance from the starting to the end position on the x and y axis touchDistance = { x: 0, y: 0 }; // The position of the slides when the touch swipe starts touchStartPosition = 0; // Stores the names of the events touchSwipeEvents = { startEvent: [ 'touchstart', 'mousedown' ], moveEvent: [ 'touchmove', 'mousemove' ], endEvent: [ 'touchend', 'mouseup' ] }; // Indicates if the thumbnail scroller is being swiped isTouchMoving = false; // Indicates whether the previous 'start' event was a 'touchstart' or 'mousedown' previousStartEvent = ''; // Stores references the event handlers in pairs containing the event identifier and the event handler // in order to be able to retrieve them when they need to be removed eventHandlerReferences = {}; // Default add-on settings defaults = { // Indicates whether the touch swipe will be enabled thumbnailTouchSwipe: true, // Sets the minimum amount that the slides should move thumbnailTouchSwipeThreshold: 50 }; constructor( slider ) { this.slider = slider; this.thumbnails = this.slider.thumbnails; this.init(); } init() { this.slider.addEventListener( 'update.' + this.namespace, this.updateHandler.bind( this ) ); } updateHandler() { this.settings = { ...this.defaults, ...this.slider.settings }; if ( this.thumbnails.isThumbnailScroller === false ) { return; } // check if touch swipe is enabled if ( this.settings.thumbnailTouchSwipe === true && this.isTouchSwipeEnabled === false ) { this.add(); } else if ( this.settings.thumbnailTouchSwipe === false && this.isTouchSwipeEnabled === true ) { this.destroy(); } } add() { this.isTouchSwipeEnabled = true; // Listen for touch swipe/mouse move events Array.from( this.touchSwipeEvents.startEvent ).forEach( ( eventType ) => { const eventHandler = ( event ) => { this.touchStartHandler( event ); }; this.eventHandlerReferences[ eventType ] = eventHandler; this.thumbnails.thumbnailsEl.addEventListener( eventType, eventHandler ); }); const dragStartHandler = ( event ) => { event.preventDefault(); }; this.eventHandlerReferences[ 'dragstart' ] = dragStartHandler; this.thumbnails.thumbnailsEl.addEventListener( 'dragstart', dragStartHandler ); // Add the grabbing icon this.thumbnails.thumbnailsEl.classList.add( 'sp-grab' ); // Remove the default thumbnailClick this.thumbnails.thumbnails.forEach( ( thumbnail ) => { thumbnail.removeEventListener( 'thumbnailClick' ); }); } // Called when the slides starts being dragged touchStartHandler( event ) { // Return if a 'mousedown' event follows a 'touchstart' event if ( event.type === 'mousedown' && this.previousStartEvent === 'touchstart' ) { this.previousStartEvent = event.type; return; } // Assign the new 'start' event this.previousStartEvent = event.type; // Disable dragging if the element is set to allow selections if ( event.target.classList.contains( 'sp-selectable' ) ) { return; } const eventObject = typeof event.touches !== 'undefined' ? event.touches[0] : event; // Prevent default behavior for mouse events if ( typeof event.touches === 'undefined' ) { event.preventDefault(); } if ( getParent( event.target, 'sp-thumbnail-container' ) !== null && getParent( event.target, 'sp-thumbnail-container' ).getElementsByTagName( 'a' ).length !== 0 ) { const links = getParent( event.target, 'sp-thumbnail-container' ).getElementsByTagName( 'a' ); // Disable click events on links const linkClickHandler = ( event ) => { event.preventDefault(); Array.from( links ).forEach( ( link ) => { link.removeEventListener( 'click', linkClickHandler ); }); }; Array.from( links ).forEach( ( link ) => { link.addEventListener( 'click', linkClickHandler ); }); this.eventHandlerReferences['click.link'] = linkClickHandler; } // Get the initial position of the mouse pointer and the initial position // of the slides' container this.touchStartPoint.x = eventObject.pageX || eventObject.clientX; this.touchStartPoint.y = eventObject.pageY || eventObject.clientY; this.touchStartPosition = this.thumbnails.thumbnailsPosition; // Clear the previous distance values this.touchDistance.x = this.touchDistance.y = 0; // If the slides are being grabbed while they're still animating, stop the // current movement if ( this.thumbnails.thumbnailsEl.classList.contains( 'sp-animated' ) ) { this.isTouchMoving = true; this.thumbnails.stopMovement(); this.touchStartPosition = this.thumbnails.thumbnailsPosition; } // Listen for move and end events Array.from( this.touchSwipeEvents.moveEvent ).forEach( ( eventType ) => { const eventHandler = ( event ) => { this.touchMoveHandler( event ); }; this.eventHandlerReferences[ eventType ] = eventHandler; this.thumbnails.thumbnailsEl.addEventListener( eventType, eventHandler ); }); Array.from( this.touchSwipeEvents.endEvent ).forEach( ( eventType ) => { const eventHandler = ( event ) => { this.touchEndHandler( event ); }; this.eventHandlerReferences[ eventType ] = eventHandler; document.addEventListener( eventType, eventHandler ); }); // Swap grabbing icons this.thumbnails.thumbnailsEl.classList.remove( 'sp-grab' ); this.thumbnails.thumbnailsEl.classList.add( 'sp-grabbing' ); this.thumbnails.thumbnailsContainerEl.classList.add( 'sp-swiping' ); } // Called during the thumbnail scroller's dragging touchMoveHandler( event ) { const eventObject = typeof event.touches !== 'undefined' ? event.touches[0] : event; // Indicate that the move event is being fired this.isTouchMoving = true; // Get the current position of the mouse pointer this.touchEndPoint.x = eventObject.pageX || eventObject.clientX; this.touchEndPoint.y = eventObject.pageY || eventObject.clientY; // Calculate the distance of the movement on both axis this.touchDistance.x = this.touchEndPoint.x - this.touchStartPoint.x; this.touchDistance.y = this.touchEndPoint.y - this.touchStartPoint.y; // Calculate the distance of the swipe that takes place in the same direction as the orientation of the thumbnails // and calculate the distance from the opposite direction. // // For a swipe to be valid there should more distance in the same direction as the orientation of the thumbnails. let distance = this.thumbnails.thumbnailsOrientation === 'horizontal' ? this.touchDistance.x : this.touchDistance.y, oppositeDistance = this.thumbnails.thumbnailsOrientation === 'horizontal' ? this.touchDistance.y : this.touchDistance.x; // If the movement is in the same direction as the orientation of the thumbnails, the swipe is valid if ( Math.abs( distance ) > Math.abs( oppositeDistance ) ) { event.preventDefault(); } else { return; } // Make the thumbnail scroller move slower if it's dragged outside its bounds if ( this.thumbnails.thumbnailsPosition >= 0 ) { const infOffset = - this.touchStartPosition; distance = infOffset + ( distance - infOffset ) * 0.2; } else if ( this.thumbnails.thumbnailsPosition <= - this.thumbnails.thumbnailsSize + this.thumbnails.thumbnailsContainerSize ) { const supOffset = this.thumbnails.thumbnailsSize - this.thumbnails.thumbnailsContainerSize + this.touchStartPosition; distance = - supOffset + ( distance + supOffset ) * 0.2; } this.thumbnails.moveTo( this.touchStartPosition + distance, true ); } // Called when the thumbnail scroller is released touchEndHandler( event ) { // Remove the 'move' and 'end' listeners Array.from( this.touchSwipeEvents.moveEvent ).forEach( ( eventType ) => { const eventHandler = this.eventHandlerReferences[ eventType ]; this.thumbnails.thumbnailsEl.removeEventListener( eventType, eventHandler ); }); Array.from( this.touchSwipeEvents.endEvent ).forEach( ( eventType ) => { const eventHandler = this.eventHandlerReferences[ eventType ]; document.removeEventListener( eventType, eventHandler ); }); // Swap grabbing icons this.thumbnails.thumbnailsEl.classList.remove( 'sp-grabbing' ); this.thumbnails.thumbnailsEl.classList.add( 'sp-grab' ); // Check if there is intention for a tap/click if ( this.isTouchMoving === false || this.isTouchMoving === true && Math.abs( this.touchDistance.x ) < 10 && Math.abs( this.touchDistance.y ) < 10 ) { let targetThumbnail = getParent( event.target, 'sp-thumbnail-container' ); if ( targetThumbnail === null ) { return; } const index = parseInt( targetThumbnail.getElementsByClassName( 'sp-thumbnail' )[0].getAttribute( 'data-index' ), 10 ); // If a link is cliked, navigate to that link, else navigate to the slide that corresponds to the thumbnail const parentLink = getParent( event.target, 'a' ); if ( parentLink !== null ) { const linkClickHandler = this.eventHandlerReferences['click.link']; parentLink.removeEventListener( 'click', linkClickHandler ); this.thumbnails.thumbnailsContainerEl.classList.remove( 'sp-swiping' ); } else if ( index !== this.thumbnails.selectedThumbnailIndex ) { this.slider.gotoSlide( index ); } return; } this.isTouchMoving = false; const clickedThumbnail = getParent( event.target, 'sp-thumbnail' ); if ( clickedThumbnail !== null ) { const thumbnailClickHandler = ( event ) => { event.preventDefault(); clickedThumbnail.removeEventListener( 'click', thumbnailClickHandler ); }; clickedThumbnail.addEventListener( 'click', thumbnailClickHandler ); } // Remove the 'sp-swiping' class but with a delay // because there might be other event listeners that check // the existence of this class, and this class should still be // applied for those listeners, since there was a swipe event setTimeout(() => { this.thumbnails.thumbnailsContainerEl.classList.remove( 'sp-swiping' ); }, 1 ); // Keep the thumbnail scroller inside the bounds if ( this.thumbnails.thumbnailsPosition > 0 ) { this.thumbnails.moveTo( 0 ); } else if ( this.thumbnails.thumbnailsPosition < this.thumbnails.thumbnailsContainerSize - this.thumbnails.thumbnailsSize ) { this.thumbnails.moveTo( this.thumbnails.thumbnailsContainerSize - this.thumbnails.thumbnailsSize ); } // Fire the 'thumbnailsMoveComplete' event this.thumbnails.dispatchEvent( 'thumbnailsMoveComplete' ); } // Destroy the module destroy() { this.isTouchSwipeEnabled = false; if ( this.thumbnails.isThumbnailScroller === false ) { return; } const dragStartHandler = this.eventHandlerReferences[ 'dragstart' ]; this.thumbnails.thumbnailsEl.removeEventListener( 'dragstart', dragStartHandler ); Array.from( [ ...this.touchSwipeEvents.startEvent , ...this.touchSwipeEvents.moveEvent ] ).forEach( ( eventType ) => { const eventHandler = this.eventHandlerReferences[ eventType ]; this.thumbnails.thumbnailsEl.removeEventListener( eventType, eventHandler ); }); Array.from( this.touchSwipeEvents.endEvent ).forEach( ( eventType ) => { const eventHandler = this.eventHandlerReferences[ eventType ]; document.removeEventListener( eventType, eventHandler ); }); this.thumbnails.thumbnailsEl.classList.remove( 'sp-grab' ); this.slider.removeEventListener( 'update.' + this.namespace ); } } export default ThumbnailTouchSwipe;