Skip to content

Commit f2655aa

Browse files
fix: carousel: update wheel handler (#557)
improves touch pad support and prevent scroll api from being spammed
1 parent e5c685c commit f2655aa

File tree

3 files changed

+34
-28
lines changed

3 files changed

+34
-28
lines changed

public/assets/GetHelp.png

5.01 KB
Loading

public/assets/NewIssue.png

-482 Bytes
Loading

src/components/Carousel/Carousel.tsx

Lines changed: 34 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ import styles from './carousel.module.scss';
4040

4141
type scrollVisibilityApiType = React.ContextType<typeof VisibilityContext>;
4242

43+
const SCROLL_LOCK_WAIT_IN_MILLISECONDS: number = 40;
44+
4345
const isVisible = (element: HTMLDivElement): boolean => {
4446
const rect: DOMRect = element.getBoundingClientRect();
4547
return (
@@ -103,7 +105,10 @@ export const Carousel: FC<CarouselProps> = React.forwardRef(
103105
const [itemsNumber, setItemsNumber] = useState<number>(0);
104106
const [visible, setVisible] = useState<boolean>();
105107
const [mouseEnter, setMouseEnter] = useState<boolean>(false);
108+
const [scrollLock, setScrollLock] = useState<boolean>(false);
109+
const timerRef = useRef<NodeJS.Timeout>();
106110
const [_single, setSingle] = useState<boolean>(single);
111+
const [_visibleElements, setVisibleElements] = useState<number>(0);
107112

108113
// ============================ Strings ===========================
109114
const [paginationLocale] = useLocaleReceiver('Pagination');
@@ -152,7 +157,7 @@ export const Carousel: FC<CarouselProps> = React.forwardRef(
152157
}, [animating]);
153158

154159
useEffect(() => {
155-
window.addEventListener('scroll', handleScroll);
160+
window.addEventListener('scroll', handleScroll, { passive: true });
156161

157162
return () => {
158163
window.removeEventListener('scroll', handleScroll);
@@ -161,16 +166,15 @@ export const Carousel: FC<CarouselProps> = React.forwardRef(
161166

162167
useEffect(() => {
163168
if (scrollMenuRef?.current) {
164-
scrollMenuRef.current.addEventListener('wheel', preventYScroll, false);
169+
// passive: false, to ensure prevent default
170+
scrollMenuRef.current.addEventListener('wheel', preventYScroll, {
171+
passive: false,
172+
});
165173
}
166174

167175
return () => {
168176
if (scrollMenuRef?.current) {
169-
scrollMenuRef.current.removeEventListener(
170-
'wheel',
171-
preventYScroll,
172-
false
173-
);
177+
scrollMenuRef.current.removeEventListener('wheel', preventYScroll);
174178
}
175179
};
176180
});
@@ -275,9 +279,9 @@ export const Carousel: FC<CarouselProps> = React.forwardRef(
275279
apiObj: scrollVisibilityApiType,
276280
event: React.WheelEvent
277281
): void => {
278-
if (event.deltaY < 0) {
282+
if (event.deltaY < 0 || event.deltaX < 0) {
279283
apiObj.scrollNextGroup();
280-
} else if (event.deltaY > 0) {
284+
} else if (event.deltaY > 0 || event.deltaX > 0) {
281285
apiObj.scrollPrevGroup();
282286
}
283287
};
@@ -286,7 +290,7 @@ export const Carousel: FC<CarouselProps> = React.forwardRef(
286290
apiObj: scrollVisibilityApiType,
287291
event: React.WheelEvent
288292
): void => {
289-
if (event.deltaY < 0) {
293+
if (event.deltaY < 0 || event.deltaX < 0) {
290294
apiObj.scrollBySingleItem(
291295
apiObj.getNextElement(),
292296
'smooth',
@@ -298,7 +302,7 @@ export const Carousel: FC<CarouselProps> = React.forwardRef(
298302
? -DEFAULT_GAP_WIDTH
299303
: OCCLUSION_AVOIDANCE_BUFFER
300304
);
301-
} else if (event.deltaY > 0) {
305+
} else if (event.deltaY > 0 || event.deltaX > 0) {
302306
const gapWidth: number = !!props.carouselScrollMenuProps?.gap
303307
? props.carouselScrollMenuProps?.gap
304308
: DEFAULT_GAP_WIDTH;
@@ -316,23 +320,25 @@ export const Carousel: FC<CarouselProps> = React.forwardRef(
316320
apiObj: scrollVisibilityApiType,
317321
event: React.WheelEvent
318322
): void => {
319-
const isTouchpad =
320-
Math.abs(event.deltaX) !== 0 || Math.abs(event.deltaY) < 15;
321-
322-
if (isTouchpad) {
323-
event.stopPropagation();
324-
return;
325-
}
326-
327-
if (_single) {
328-
handleSingleItemScrollOnWheel(apiObj, event);
329-
} else {
330-
handleGroupScrollOnWheel(apiObj, event);
323+
// Prevent spamming of scroll.
324+
clearTimeout(timerRef.current);
325+
timerRef.current = setTimeout(
326+
() => setScrollLock(false),
327+
SCROLL_LOCK_WAIT_IN_MILLISECONDS
328+
);
329+
if (!scrollLock) {
330+
setScrollLock(true);
331+
if (_single) {
332+
handleSingleItemScrollOnWheel(apiObj, event);
333+
} else {
334+
handleGroupScrollOnWheel(apiObj, event);
335+
}
331336
}
332337
};
333338

334339
const preventYScroll = (event: { preventDefault: () => void }): void => {
335-
if (mouseEnter) {
340+
// Prevent document scroll only when hovering over a carousel that may be scrolled.
341+
if (mouseEnter && (previousButtonRef.current || nextButtonRef.current)) {
336342
event.preventDefault();
337343
}
338344
};
@@ -508,6 +514,7 @@ export const Carousel: FC<CarouselProps> = React.forwardRef(
508514
if (initComplete && visibleElements?.length) {
509515
setPreviousDisabled(isFirstItemVisible);
510516
setNextDisabled(isLastItemVisible);
517+
setVisibleElements(visibleElements.length);
511518
}
512519
}, [
513520
initComplete,
@@ -540,10 +547,9 @@ export const Carousel: FC<CarouselProps> = React.forwardRef(
540547
return;
541548
}
542549

543-
console.log(scrollMenuRef.current?.offsetWidth);
544-
545550
if (!single) {
546-
setSingle(scrollMenuRef.current?.offsetWidth <= 680);
551+
// If the number of visible elements is less than 3, swap to single scroll.
552+
setSingle(_visibleElements < 3);
547553
}
548554
};
549555

@@ -552,7 +558,7 @@ export const Carousel: FC<CarouselProps> = React.forwardRef(
552558
return;
553559
}
554560
updateScrollMode();
555-
}, [_single]);
561+
}, [_single, _visibleElements]);
556562

557563
return (
558564
<LocaleReceiver componentName={'Pagination'} defaultLocale={enUS}>

0 commit comments

Comments
 (0)