From ae30d40e626fb35fc83769f4caf1c95d48e58396 Mon Sep 17 00:00:00 2001 From: Alessandro Date: Thu, 24 Oct 2024 22:25:14 +0200 Subject: [PATCH] poc: more responsive carousel pagination dots (#2220) * chore(carousel): more responsive dots * chore: minor change * chore: update * chore: update * chore: minor change --- src/components/carousel/carousel.component.ts | 48 ++++++++++++------- 1 file changed, 31 insertions(+), 17 deletions(-) diff --git a/src/components/carousel/carousel.component.ts b/src/components/carousel/carousel.component.ts index dcae6044..6237888c 100644 --- a/src/components/carousel/carousel.component.ts +++ b/src/components/carousel/carousel.component.ts @@ -94,6 +94,7 @@ export default class SlCarousel extends ShoelaceElement { private autoplayController = new AutoplayController(this, () => this.next()); private readonly localize = new LocalizeController(this); private mutationObserver: MutationObserver; + private pendingSlideChange = false; connectedCallback(): void { super.connectedCallback(); @@ -261,6 +262,9 @@ export default class SlCarousel extends ShoelaceElement { @eventOptions({ passive: true }) private handleScroll() { this.scrolling = true; + if (!this.pendingSlideChange) { + this.synchronizeSlides(); + } } /** @internal Synchronizes the slides with the IntersectionObserver API. */ @@ -277,20 +281,28 @@ export default class SlCarousel extends ShoelaceElement { } const firstIntersecting = entries.find(entry => entry.isIntersecting); + if (!firstIntersecting) { + return; + } - if (firstIntersecting) { + const slidesWithClones = this.getSlides({ excludeClones: false }); + const slidesCount = this.getSlides().length; + + // Update the current index based on the first visible slide + const slideIndex = slidesWithClones.indexOf(firstIntersecting.target as SlCarouselItem); + // Normalize the index to ignore clones + const normalizedIndex = this.loop ? slideIndex - this.slidesPerPage : slideIndex; + + // Set the index to the closest "snappable" slide + this.activeSlide = + (Math.ceil(normalizedIndex / this.slidesPerMove) * this.slidesPerMove + slidesCount) % slidesCount; + + if (!this.scrolling) { if (this.loop && firstIntersecting.target.hasAttribute('data-clone')) { const clonePosition = Number(firstIntersecting.target.getAttribute('data-clone')); // Scrolls to the original slide without animating, so the user won't notice that the position has changed this.goToSlide(clonePosition, 'instant'); - } else { - const slides = this.getSlides(); - - // Update the current index based on the first visible slide - const slideIndex = slides.indexOf(firstIntersecting.target as SlCarouselItem); - // Set the index to the first "snappable" slide - this.activeSlide = Math.ceil(slideIndex / this.slidesPerMove) * this.slidesPerMove; } } }, @@ -307,10 +319,9 @@ export default class SlCarousel extends ShoelaceElement { private handleScrollEnd() { if (!this.scrolling || this.dragging) return; - - this.synchronizeSlides(); - this.scrolling = false; + this.pendingSlideChange = false; + this.synchronizeSlides(); } private isCarouselItem(node: Node): node is SlCarouselItem { @@ -380,7 +391,7 @@ export default class SlCarousel extends ShoelaceElement { } @watch('activeSlide') - handelSlideChange() { + handleSlideChange() { const slides = this.getSlides(); slides.forEach((slide, i) => { slide.classList.toggle('--is-active', i === this.activeSlide); @@ -485,11 +496,14 @@ export default class SlCarousel extends ShoelaceElement { const nextLeft = nextSlideRect.left - scrollContainerRect.left; const nextTop = nextSlideRect.top - scrollContainerRect.top; - scrollContainer.scrollTo({ - left: nextLeft + scrollContainer.scrollLeft, - top: nextTop + scrollContainer.scrollTop, - behavior - }); + if (nextLeft || nextTop) { + this.pendingSlideChange = true; + scrollContainer.scrollTo({ + left: nextLeft + scrollContainer.scrollLeft, + top: nextTop + scrollContainer.scrollTop, + behavior + }); + } } render() {