Skip to content

Commit

Permalink
Improve the stability of swiping
Browse files Browse the repository at this point in the history
  • Loading branch information
carbonrobot committed Oct 7, 2024
1 parent afb70e1 commit f729341
Show file tree
Hide file tree
Showing 5 changed files with 82 additions and 10 deletions.
5 changes: 5 additions & 0 deletions .changeset/wet-schools-remember.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'nuka-carousel': patch
---

Improve the stability of swiping
28 changes: 27 additions & 1 deletion docs/api/swiping.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@ By default the carousel will allow you to drag the carousel to the next slide. Y

| Prop Name | Type | Default Value |
| :--------- | :-------- | :------------ |
| `minSwipeDistance` | `number` | 10 |
| `swiping` | `boolean` | `true` |

### Enabled (default)
### Enabled with `scrollDistance="slide"`

<Carousel scrollDistance="slide" showDots>
<div className="demo-slide bg-green-500" />
Expand All @@ -39,6 +40,31 @@ By default the carousel will allow you to drag the carousel to the next slide. Y
</Carousel>
```

### Enabled with `scrollDistance="screen"`

<Carousel scrollDistance="screen" showDots>
<div className="demo-slide bg-green-500" />
<div className="demo-slide bg-red-500" />
<div className="demo-slide bg-blue-500" />
<div className="demo-slide bg-yellow-500" />
<div className="demo-slide bg-gray-500" />
<div className="demo-slide bg-orange-500" />
<div className="demo-slide bg-blue-700" />
<div className="demo-slide bg-red-900" />
<div className="demo-slide bg-gray-800" />
<div className="demo-slide bg-green-500" />
</Carousel>

#### Code

```tsx
<Carousel scrollDistance="screen" showDots>
<img src="pexels-01.jpg" />
<img src="pexels-02.jpg" />
<img src="pexels-03.jpg" />
</Carousel>
```

### Disabled

<Carousel scrollDistance="slide" showDots swiping={false}>
Expand Down
53 changes: 45 additions & 8 deletions packages/nuka/src/Carousel/Carousel.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
import { forwardRef, useEffect, useImperativeHandle, useRef } from 'react';
import {
TouchEvent,
forwardRef,
useEffect,
useImperativeHandle,
useRef,
useState,
} from 'react';

import { useInterval } from '../hooks/use-interval';
import { usePaging } from '../hooks/use-paging';
Expand All @@ -22,6 +29,7 @@ const defaults = {
dots: <PageIndicators />,
id: 'nuka-carousel',
keyboard: true,
minSwipeDistance: 10,
scrollDistance: 'screen',
showArrows: false,
showDots: false,
Expand All @@ -44,6 +52,7 @@ export const Carousel = forwardRef<SlideHandle, CarouselProps>(
dots,
id,
keyboard,
minSwipeDistance,
scrollDistance,
showArrows,
showDots,
Expand All @@ -69,15 +78,41 @@ export const Carousel = forwardRef<SlideHandle, CarouselProps>(
});

// -- handle touch scroll events
const onContainerScroll = useDebounced(() => {
const [touchStart, setTouchStart] = useState<null | number>(null);
const [touchEnd, setTouchEnd] = useState<null | number>(null);

const onTouchStart = (e: TouchEvent<HTMLDivElement>) => {
setTouchEnd(null);
setTouchStart(e.targetTouches[0].clientX);
};

const onTouchMove = (e: TouchEvent<HTMLDivElement>) =>
setTouchEnd(e.targetTouches[0].clientX);

const onTouchEnd = useDebounced(() => {
if (!containerRef.current) return;
if (!touchStart || !touchEnd) return;

// find the closest page index based on the scroll position
const scrollLeft = containerRef.current.scrollLeft;
const closestPageIndex = scrollOffset.indexOf(
nint(scrollOffset, scrollLeft),
);
goToPage(closestPageIndex);

const distance = touchStart - touchEnd;
const isLeftSwipe = distance > minSwipeDistance;
const isRightSwipe = distance < -minSwipeDistance;
if (isLeftSwipe || isRightSwipe) {
const closestPageIndex = scrollOffset.indexOf(
nint(scrollOffset, scrollLeft),
);

if (isLeftSwipe) {
const nextPage = Math.min(closestPageIndex + 1, totalPages);
containerRef.current.scrollLeft = scrollOffset[nextPage];
goToPage(nextPage);
} else {
const prevPage = Math.max(closestPageIndex - 1, 0);
containerRef.current.scrollLeft = scrollOffset[prevPage];
goToPage(prevPage);
}
}
}, 100);

// -- keyboard nav
Expand Down Expand Up @@ -147,7 +182,9 @@ export const Carousel = forwardRef<SlideHandle, CarouselProps>(
<div
className="nuka-overflow"
ref={containerRef}
onTouchMove={onContainerScroll}
onTouchEnd={onTouchEnd}
onTouchMove={onTouchMove}
onTouchStart={onTouchStart}
id="nuka-overflow"
data-testid="nuka-overflow"
style={{ touchAction: swiping ? 'pan-x' : 'none' }}
Expand Down
1 change: 1 addition & 0 deletions packages/nuka/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export type CarouselProps = CarouselCallbacks & {
dots?: ReactNode;
id?: string;
keyboard?: boolean;
minSwipeDistance?: number;
scrollDistance?: ScrollDistanceType;
showArrows?: ShowArrowsOption;
showDots?: boolean;
Expand Down
5 changes: 4 additions & 1 deletion website/src/components/landing/landing-features.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@ export const LandingFeatures = ({
<h2 className="my-8 text-4xl font-semibold">{heading}</h2>
<ul className="grid grid-cols-3 items-start content-start justify-items-start justify-between gap-12 list-none pl-0">
{list.map(({ alt, body, imgSrc, title }) => (
<li className="col-span-3 md:col-span-1 flex flex-col items-center text-center">
<li
className="col-span-3 md:col-span-1 flex flex-col items-center text-center"
key={title}
>
<img src={imgSrc} alt={alt} className="max-h-72" />
<span className="mt-8 text-2xl font-semibold">{title}</span>
<span className="mt-2 text-lg leading-8 mx-3">{body}</span>
Expand Down

0 comments on commit f729341

Please sign in to comment.