diff --git a/src/slider/index.html b/src/slider/index.html index c53dc8a..421a93e 100644 --- a/src/slider/index.html +++ b/src/slider/index.html @@ -33,7 +33,7 @@
- + @@ -63,7 +63,7 @@
- + @@ -92,7 +92,7 @@
- + @@ -162,14 +162,14 @@ - + - + diff --git a/src/slider/style.scss b/src/slider/style.scss index f78544f..e27b1ec 100644 --- a/src/slider/style.scss +++ b/src/slider/style.scss @@ -13,6 +13,10 @@ tp-slider-slides { position: relative; display: flex; align-items: flex-start; + overflow-y: visible; + overflow-x: auto; + scroll-snap-type: x mandatory; + scrollbar-width: none; tp-slider:not([resizing="yes"]) & { transition-duration: 0.6s; diff --git a/src/slider/tp-slider.ts b/src/slider/tp-slider.ts index a5fa1ac..d7d6825 100644 --- a/src/slider/tp-slider.ts +++ b/src/slider/tp-slider.ts @@ -15,9 +15,8 @@ export class TPSliderElement extends HTMLElement { /** * Properties. */ - protected touchStartX: number = 0; - protected touchStartY: number = 0; - protected swipeThreshold: number = 200; + protected slidesScrollContainer: TPSliderSlidesElement | null; + protected slidesScrollContainerRect: DOMRect | {}; protected responsiveSettings: { [ key: string ]: any }; protected allowedResponsiveKeys: string[] = [ 'flexible-height', @@ -29,6 +28,7 @@ export class TPSliderElement extends HTMLElement { 'step', 'responsive', ]; + protected isProgramaticScroll: boolean = false; /** * Constructor. @@ -36,15 +36,17 @@ export class TPSliderElement extends HTMLElement { constructor() { // Initialize parent. super(); + this.slidesScrollContainer = this.querySelector( 'tp-slider-slides' ); + this.slidesScrollContainerRect = this.slidesScrollContainer?.getBoundingClientRect() ?? {}; + + // Add event listener to handle current slide attribute on scroll. + this.slidesScrollContainer?.addEventListener( 'scroll', this.handleCurrentSlideOnScroll.bind( this ) ); // Set current slide. if ( ! this.getAttribute( 'current-slide' ) ) { this.setAttribute( 'current-slide', '1' ); } - // Threshold Setting. - this.swipeThreshold = Number( this?.getAttribute( 'swipe-threshold' ) ?? '200' ); - // Initialize slider. this.slide(); this.autoSlide(); @@ -65,10 +67,18 @@ export class TPSliderElement extends HTMLElement { window.addEventListener( 'resize', this.handleResize.bind( this ) ); document.fonts.ready.then( () => this.handleResize() ); } + } - // Touch listeners. - this.addEventListener( 'touchstart', this.handleTouchStart.bind( this ), { passive: true } ); - this.addEventListener( 'touchend', this.handleTouchEnd.bind( this ) ); + /** + * Flag programmatic scroll to prevent scroll handling. + */ + protected flagProgramaticScroll(): void { + // Flag programmatic scroll. + this.isProgramaticScroll = true; + setTimeout( () => { + // Unflag programmatic scroll. + this.isProgramaticScroll = false; + }, 500 ); } /** @@ -92,7 +102,7 @@ export class TPSliderElement extends HTMLElement { */ static get observedAttributes(): string[] { // Observed attributes. - return [ 'current-slide', 'flexible-height', 'infinite', 'swipe', 'per-view', 'step' ]; + return [ 'current-slide', 'flexible-height', 'infinite', 'per-view', 'step' ]; } /** @@ -331,6 +341,9 @@ export class TPSliderElement extends HTMLElement { * @protected */ protected slide(): void { + // Flag programmatic scroll. + this.flagProgramaticScroll(); + // Check if slider is disabled. if ( 'yes' === this.getAttribute( 'disabled' ) ) { // Yes, it is. So stop. @@ -358,7 +371,10 @@ export class TPSliderElement extends HTMLElement { // Check if behaviour is set to fade and slide on the current slide index is present in the slides array. if ( 'fade' !== behaviour && slides[ this.currentSlideIndex - 1 ] ) { // Yes, it is. So slide to the current slide. - slidesContainer.style.left = `-${ slides[ this.currentSlideIndex - 1 ].offsetLeft }px`; + slidesContainer.scroll( { + left: slides[ this.currentSlideIndex - 1 ].offsetLeft - slides[ 0 ].offsetLeft, + behavior: 'smooth', + } ); } } @@ -487,6 +503,59 @@ export class TPSliderElement extends HTMLElement { } } + /** + * Handle current slide attribute on scroll. + * + * This is to handle the case when the user scrolls the slides track + * and we need to update the current slide index based on the scroll position. + */ + handleCurrentSlideOnScroll() { + // Check if this is a programmatic scroll. + if ( this.isProgramaticScroll ) { + // Early return to prevent handling programmatic scroll. + return; + } + + // Check if we have scroll container. + if ( ! this.slidesScrollContainer ) { + // No scroll container, early return. + return; + } + + // Get all the slides. + const slides : NodeListOf | null = this.querySelectorAll( 'tp-slider-slide' ); + + // If no slides. + if ( 0 === slides.length ) { + // Early return. + return; + } + + // Check if scroll position is at right end. + const isAtRightEnd = Math.abs( this.slidesScrollContainer.scrollLeft + this.slidesScrollContainer.clientWidth - this.slidesScrollContainer.scrollWidth ) < 1; + + // Conditionally set the current slide index based on the scroll position. + if ( isAtRightEnd ) { + // If the current slide index is equal to the total number of slides, set it to the last slide. + this.setCurrentSlide( slides.length ); + } else { + // Loop through all slides and check which slide is intersecting with the left edge of the scroll container. + slides.forEach( ( slide: TPSliderSlideElement, index: number ) => { + // Get the bounding rectangle of the slide. + const slideRect = slide.getBoundingClientRect(); + + // Check if the slide is aligned with the left edge of the scroll container. + const isAlignedWithLeftEdge = Math.abs( slideRect?.left - ( this.slidesScrollContainerRect as DOMRect ).left ) < 1; + + // Check if the slide is intersecting with the left edge of the scroll container. + if ( isAlignedWithLeftEdge ) { + // Yes, it is. So set the current slide index. + this.setCurrentSlide( index + 1 ); + } + } ); + } + } + /** * Update the height of the slider based on current slide. */ @@ -624,64 +693,6 @@ export class TPSliderElement extends HTMLElement { } ); } - /** - * Detect touch start event, and store the starting location. - * - * @param {Event} e Touch event. - * - * @protected - */ - protected handleTouchStart( e: TouchEvent ): void { - // initialize touch start coordinates - if ( 'yes' === this.getAttribute( 'swipe' ) ) { - this.touchStartX = e.touches[ 0 ].clientX; - this.touchStartY = e.touches[ 0 ].clientY; - } - } - - /** - * Detect touch end event, and check if it was a left or right swipe. - * - * @param {Event} e Touch event. - * - * @protected - */ - protected handleTouchEnd( e: TouchEvent ): void { - // Early return if swipe is not enabled. - if ( 'yes' !== this.getAttribute( 'swipe' ) ) { - // Early return. - return; - } - - // Calculate the horizontal and vertical distance moved. - const touchEndX: number = e.changedTouches[ 0 ].clientX; - const touchEndY: number = e.changedTouches[ 0 ].clientY; - const swipeDistanceX: number = touchEndX - this.touchStartX; - const swipeDistanceY: number = touchEndY - this.touchStartY; - - // Determine if the swipe is predominantly horizontal or vertical. - const isHorizontalSwipe: boolean = Math.abs( swipeDistanceX ) > Math.abs( swipeDistanceY ); - - // If it's not horizontal swipe, return - if ( ! isHorizontalSwipe ) { - // Early return. - return; - } - - // Check if it's a right or left swipe. - if ( swipeDistanceX > 0 ) { - // Right-Swipe: Check if horizontal swipe distance is less than the threshold. - if ( swipeDistanceX < this.swipeThreshold ) { - this.previous(); - } - } else if ( swipeDistanceX < 0 ) { - // Left-Swipe: Check if horizontal swipe distance is less than the threshold. - if ( swipeDistanceX > -this.swipeThreshold ) { - this.next(); - } - } - } - /** * Auto slide. */