UNPKG

accordion-slider-js

Version:

Modular accordion slider built in Vanilla JavaScript.

298 lines (231 loc) 12.6 kB
import { getParent, resolveUnit } from '../../helpers/util.js'; class TouchSwipe { // The namespace to be used when adding event listeners namespace = 'touchswipe'; // Reference to the base accordion instance accordion; // Stores the current settings of the accordion settings; // Indicates whether the touch swipe is enabled for slides 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; // Indicates if the slides are being swiped isTouchMoving = false; // Stores the names of the events touchSwipeEvents = { startEvent: [ 'touchstart', 'mousedown' ], moveEvent: [ 'touchmove', 'mousemove' ], endEvent: [ 'touchend', 'mouseup' ] }; // 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 touchSwipe: true, // Sets the minimum amount that the slides should move touchSwipeThreshold: 50 }; constructor( accordion ) { this.accordion = accordion; this.init(); } init() { this.accordion.addEventListener( 'update.' + this.namespace, this.updateHandler.bind( this ) ); } updateHandler() { this.settings = { ...this.defaults, ...this.accordion.settings }; // check if touch swipe is enabled if ( this.settings.touchSwipe === true && this.isTouchSwipeEnabled === false ) { this.add(); } else if ( this.settings.touchSwipe === false && this.isTouchSwipeEnabled === true ) { this.destroy(); } // Add the grabbing icon if ( this.accordion.getTotalPages() > 1 ) { this.accordion.panelsContainerEl.classList.add( 'as-grab' ); } else { this.accordion.panelsContainerEl.classList.remove( 'as-grab' ); } } add() { this.isTouchSwipeEnabled = true; // Listen for touch swipe/mouse move events Array.from( this.touchSwipeEvents.startEvent ).forEach( ( eventType ) => { this.accordion.panelsContainerEl.addEventListener( eventType, this.eventHandlerReferences[ eventType ] = ( event ) => { this.touchStartHandler( event ); }); }); this.accordion.panelsContainerEl.addEventListener( 'dragstart', this.eventHandlerReferences[ 'dragstart' ] = ( event ) => { event.preventDefault(); }); // Prevent 'click' events unless there is intention for a 'click' Array.from( this.accordion.panelsContainerEl.getElementsByTagName( 'a' ) ).forEach( ( element ) => { element.addEventListener( 'click', this.eventHandlerReferences[ 'click.link' ] = ( event ) => { if ( this.accordion.accordionEl.classList.contains( 'as-swiping' ) ) { event.preventDefault(); } }); }); // prevent 'tap' events unless the panel is opened this.accordion.panelsContainerEl.addEventListener( 'touchstart', this.eventHandlerReferences[ 'touchstart.link' ] = () => { const disabledLinks = this.accordion.panelsContainerEl.querySelectorAll( '[data-disabledlink]' ); Array.from( disabledLinks ).forEach( ( linkEl ) => { linkEl.style.removeProperty( 'pointer-events' ); linkEl.removeAttribute( 'data-disabledlink' ); }); }); Array.from( this.accordion.panelsContainerEl.getElementsByTagName( 'a' ) ).forEach( ( linkEl ) => { linkEl.addEventListener( 'touchend', this.eventHandlerReferences[ 'touchend.link' ] = () => { const parentPanel = getParent( linkEl, 'as-panel' ); if ( parentPanel !== null && parentPanel.classList.contains( 'as-opened' ) === false ) { linkEl.style[ 'pointer-events' ] = 'none'; linkEl.setAttribute( 'data-disabledlink', true ); } }); }); } // 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( 'as-selectable' ) ) { return; } const eventObject = typeof event.touches !== 'undefined' ? event.touches[0] : event; const positionProperty = this.settings.orientation === 'horizontal' ? 'left' : 'top'; // 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 = parseInt( this.accordion.panelsContainerEl.style[ positionProperty ], 10 ); // Clear the previous distance values this.touchDistance.x = this.touchDistance.y = 0; // Listen for move and end events Array.from( this.touchSwipeEvents.moveEvent ).forEach( ( eventType ) => { this.accordion.panelsContainerEl.addEventListener( eventType, this.eventHandlerReferences[ eventType ] = ( event ) => { this.touchMoveHandler( event ); }); }); Array.from( this.touchSwipeEvents.endEvent ).forEach( ( eventType ) => { document.addEventListener( eventType, this.eventHandlerReferences[ eventType ] = ( event ) => { this.touchEndHandler( event ); }); }); // Swap grabbing icons this.accordion.panelsContainerEl.classList.replace( 'as-grab', 'as-grabbing' ); } // Called during the slides' dragging touchMoveHandler( event ) { const eventObject = typeof event.touches !== 'undefined' ? event.touches[0] : event; const positionProperty = this.settings.orientation === 'horizontal' ? 'left' : 'top'; // Indicate that the move event is being fired this.isTouchMoving = true; // Add 'as-swiping' class to indicate that the slides are being swiped if ( this.accordion.accordionEl.classList.contains( 'as-swiping' ) === false ) { this.accordion.accordionEl.classList.add( 'as-swiping' ); } // 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 panels // 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 panels. let distance = this.settings.orientation === 'horizontal' ? this.touchDistance.x : this.touchDistance.y; // If opposite scrolling is still allowed, the swipe wasn't valid, so return. if ( this.allowOppositeScrolling === true ) { return; } // Don't allow opposite scrolling event.preventDefault(); // get the current position of panels' container const currentPanelsPosition = parseInt( this.accordion.panelsContainerEl.style[ positionProperty ], 10 ); // reduce the movement speed if the panels' container is outside its bounds if ( ( currentPanelsPosition >= 0 && this.accordion.currentPage === 0 ) || ( currentPanelsPosition <= - this.accordion.totalPanelsSize + this.accordion.totalSize && this.accordion.currentPage === this.accordion.getTotalPages() - 1 ) ) { distance = distance * 0.2; } // move the panels' container this.accordion.panelsContainerEl.style[ positionProperty ] = resolveUnit( this.touchStartPosition + distance ); } // Called when the slides are released touchEndHandler() { let touchDistance = this.settings.orientation === 'horizontal' ? this.touchDistance.x : this.touchDistance.y; const positionProperty = this.settings.orientation === 'horizontal' ? 'left' : 'top'; // Remove the 'move' and 'end' listeners Array.from( this.touchSwipeEvents.moveEvent ).forEach( ( eventType ) => { this.accordion.panelsContainerEl.removeEventListener( eventType, this.eventHandlerReferences[ eventType ] ); }); Array.from( this.touchSwipeEvents.endEvent ).forEach( ( eventType ) => { document.removeEventListener( eventType, this.eventHandlerReferences[ eventType ] ); }); // Swap grabbing icons this.accordion.panelsContainerEl.classList.replace( 'as-grabbing', 'as-grab' ); // Remove the 'as-swiping' class with a delay, to allow // other event listeners (i.e. click) to check the existence // of the swipe event. if ( this.accordion.accordionEl.classList.contains( 'as-swiping' ) ) { setTimeout(() => { this.accordion.accordionEl.classList.remove( 'as-swiping' ); }, 100 ); } // Return if the slides didn't move if ( this.isTouchMoving === false ) { return; } this.isTouchMoving = false; if ( touchDistance > this.settings.touchSwipeThreshold) { if ( this.accordion.currentPage > 0 ) { this.accordion.previousPage(); } else { this.accordion.panelsContainerEl.style[ positionProperty ] = this.touchStartPosition; } } else if ( - touchDistance > this.settings.touchSwipeThreshold) { if ( this.accordion.currentPage < this.accordion.getTotalPages() - 1 ) { this.accordion.nextPage(); } else { this.accordion.gotoPage( this.accordion.currentPage ); } } else if ( Math.abs( touchDistance ) < this.settings.touchSwipeThreshold ) { this.accordion.panelsContainerEl.style[ positionProperty ] = resolveUnit( this.touchStartPosition ); } } // Destroy the module destroy() { this.isTouchSwipeEnabled = false; this.accordion.removeEventListener( 'update.' + this.namespace ); this.accordion.panelsContainerEl.removeEventListener( 'dragstart', this.eventHandlerReferences[ 'dragstart' ] ); this.accordion.panelsContainerEl.removeEventListener( 'touchstart', this.eventHandlerReferences[ 'touchstart.link' ] ); Array.from( this.accordion.panelsContainerEl.getElementsByTagName( 'a' ) ).forEach( ( linkEl ) => { linkEl.removeEventListener( 'click', this.eventHandlerReferences[ 'click.link' ] ); linkEl.removeEventListener( 'touchend', this.eventHandlerReferences[ 'touchend.link' ] ); }); Array.from( [ ...this.touchSwipeEvents.startEvent , ...this.touchSwipeEvents.moveEvent ] ).forEach( ( eventType ) => { this.accordion.panelsContainerEl.removeEventListener( eventType, this.eventHandlerReferences[ eventType ] ); }); Array.from( this.touchSwipeEvents.endEvent ).forEach( ( eventType ) => { document.removeEventListener( eventType, this.eventHandlerReferences[ eventType ] ); }); this.accordion.panelsContainerEl.classList.remove( 'as-grab' ); } } export default TouchSwipe;