improve touchscreen scrolling on carousel
All checks were successful
publish.yml / publish (push) Successful in 1m5s

This commit is contained in:
2026-04-16 23:01:27 +02:00
parent e5474716ec
commit 7288edd019
2 changed files with 52 additions and 2 deletions

View File

@@ -10,8 +10,6 @@
(mouseleave)="onHoverEnd()"
(focusin)="onHoverStart()"
(focusout)="onCarouselFocusOut($event)"
(touchstart)="onHoverStart()"
(touchend)="onHoverEnd()"
(click)="onInteractionClick()"
>
<button class="carousel-btn" type="button" aria-label="Previous technologies" (click)="scrollStack(-1)">
@@ -24,6 +22,9 @@
[class.is-auto-scrolling]="isAutoScrolling"
#stackCarousel
(scroll)="onCarouselScroll()"
(touchstart)="onTouchStart()"
(touchend)="onTouchEnd()"
(touchcancel)="onTouchCancel()"
role="region"
aria-label="Tech stack carousel"
>

View File

@@ -26,9 +26,12 @@ export class TechStackCarousel implements AfterViewInit, OnDestroy {
private currentAutoScrollSpeedPxPerSecond = 0;
private targetAutoScrollSpeedPxPerSecond = 0;
private isPausePendingStop = false;
private isTouchInteracting = false;
private isProgrammaticScrollUpdate = false;
private readonly autoScrollSpeedPxPerSecond = 72;
private readonly speedRampDurationMs = 420;
private readonly speedStopThresholdPxPerSecond = 0.5;
private readonly touchScrollResumeDelayMs = 1200;
isAutoScrolling = false;
showLeftFade = false;
showRightFade = false;
@@ -89,9 +92,34 @@ export class TechStackCarousel implements AfterViewInit, OnDestroy {
}
onCarouselScroll(): void {
if (this.isProgrammaticScrollUpdate) {
this.isProgrammaticScrollUpdate = false;
this.updateFadeState();
return;
}
// Keep autoscroll from fighting touch/momentum scrolling on mobile.
this.pauseAutoScrollImmediately();
this.scheduleResume(this.touchScrollResumeDelayMs);
this.updateFadeState();
}
onTouchStart(): void {
this.isTouchInteracting = true;
this.clearScheduledResume();
this.pauseAutoScrollImmediately();
}
onTouchEnd(): void {
this.isTouchInteracting = false;
this.scheduleResume(this.touchScrollResumeDelayMs);
}
onTouchCancel(): void {
this.isTouchInteracting = false;
this.scheduleResume(this.touchScrollResumeDelayMs);
}
onHoverStart(): void {
this.pauseAutoScroll();
this.clearScheduledResume();
@@ -118,6 +146,11 @@ export class TechStackCarousel implements AfterViewInit, OnDestroy {
}
resumeAutoScroll(): void {
if (this.isTouchInteracting) {
this.scheduleResume(this.touchScrollResumeDelayMs);
return;
}
this.isPausePendingStop = false;
this.targetAutoScrollSpeedPxPerSecond = this.autoScrollSpeedPxPerSecond;
this.syncAutoScrollState();
@@ -242,6 +275,7 @@ export class TechStackCarousel implements AfterViewInit, OnDestroy {
// Normalize only when moving past the duplicated track boundary to avoid abrupt remaps on quick re-entry.
this.virtualScrollLeft = nextVirtualScrollLeft >= loopWidth ? nextVirtualScrollLeft - loopWidth : nextVirtualScrollLeft;
this.isProgrammaticScrollUpdate = true;
carousel.scrollLeft = this.virtualScrollLeft;
}
@@ -277,5 +311,20 @@ export class TechStackCarousel implements AfterViewInit, OnDestroy {
this.targetAutoScrollSpeedPxPerSecond > this.speedStopThresholdPxPerSecond ||
this.currentAutoScrollSpeedPxPerSecond > this.speedStopThresholdPxPerSecond;
}
private pauseAutoScrollImmediately(): void {
this.clearScheduledResume();
if (this.autoScrollFrameId !== null) {
cancelAnimationFrame(this.autoScrollFrameId);
this.autoScrollFrameId = null;
}
this.isPausePendingStop = false;
this.currentAutoScrollSpeedPxPerSecond = 0;
this.targetAutoScrollSpeedPxPerSecond = 0;
this.lastAutoScrollTime = 0;
this.syncAutoScrollState();
}
}