Skip to content

Commit

Permalink
refactor: improve and add comments
Browse files Browse the repository at this point in the history
  • Loading branch information
2wheeh committed Apr 26, 2024
1 parent fb10662 commit 0e0f649
Showing 1 changed file with 92 additions and 53 deletions.
145 changes: 92 additions & 53 deletions ui/src/hooks/common/use-swiper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,27 +8,91 @@ import { getTouchEventData } from '@/lib/utils/common/get-touch-event-data';

const MIN_SWIPE_REQUIRED = 40; // px

/*
<When current slide is slide1 scenario>
\\\\ = current slide.
On touch start, values are initialized:
containerWidth = cw
scrollWidth = 3cw
minOffsetX = containerWidth - scrollWidth = -2cw
initialOffsetX = -cw
-2cw -cw 0 container cw 2cw 3cw
|-------------------|-------------------|-------------------|-------------------|-------------------|
| slide0 |\\\\\ slide1 \\\\\| slide2 |
|-------------------|-------------------|-------------------|
| <---------------------- scrollWidth --------------------> |
offsetX <= initialOffsetX
On touch move, a new offsetX is calculated as follows:
swipeDiff = touchStartX - currentTouchX
newOffsetX = initialOffsetX - swipeDiff
here, newOffsetX is limited by minOffsetX and maxOffsetX !
maxOffsetX => 0 container cw 2cw 3cw
|-------------------|-------------------|-------------------|
|\\\\\ slide0 \\\\\| slide1 | slide2 |
|-------------------|-------------------|-------------------|
offsetX
-2cw <= minOffsetX -cw 0 container cw
|-------------------|-------------------|-------------------|
| slide0 | slide1 |\\\\\ slide2 \\\\\|
|-------------------|-------------------|-------------------|
offsetX
On touch end, the slides are moved:
if swipeDiff > MIN_SWIPE_REQUIRED, caculate the index of the slide to move
newOffsetX = (index of the slide to move) * containerWidth
*/

export function useSwiper() {
const [offsetX, setOffsetX, offsetXRef] = useStateRef(0);

const containerRef = useRef<HTMLUListElement>(null);
// It is referenced in `onIndicatorClick`, which is binded on first render.
// So, it needs to be a ref to keep the latest value in the closure.
// Otherwise, `onIndicatorClick` will always have the initial value which is 0.
const containerWidthRef = useRef(0);
const minOffsetXRef = useRef(0);
const currentOffsetXRef = useRef(0);
const startXRef = useRef(0);

const [offsetX, setOffsetX, offsetXRef] = useStateRef(0);

const [isSwiping, setIsSwiping] = useState(false);
const [currentIdx, setCurrentIdx] = useState(0);

// minOffsetX, initialOffsetX, touchStartX are set in onTouchStart
// and referenced in onTouchMove and onTouchEnd,
// The value of these don't change after they passed to closure (onTouchMove and onTouchEnd).
// So, they don't need to be refs.
let minOffsetX: number;
let initialOffsetX: number;
let touchStartX: number;

const onTouchStart = (e: React.TouchEvent<HTMLDivElement> | React.MouseEvent<HTMLDivElement>) => {
setIsSwiping(true);

const containerEl = containerRef.current!;

containerWidthRef.current = containerEl.offsetWidth;

minOffsetX = containerWidthRef.current - containerEl.scrollWidth;

initialOffsetX = offsetXRef.current!;
touchStartX = getTouchEventData(e).clientX;

window.addEventListener('touchmove', onTouchMove);
window.addEventListener('touchend', onTouchEnd);
window.addEventListener('mousemove', onTouchMove);
window.addEventListener('mouseup', onTouchEnd);
};

const onTouchMove = (e: globalThis.TouchEvent | globalThis.MouseEvent) => {
const currentX = getTouchEventData(e).clientX;
const diff = startXRef.current - currentX;
let newOffsetX = currentOffsetXRef.current - diff;
const currentTouchX = getTouchEventData(e).clientX;
const swipeDiff = touchStartX - currentTouchX;
let newOffsetX = initialOffsetX - swipeDiff;

const maxOffsetX = 0;
const minOffsetX = minOffsetXRef.current;

// TODO: support circular swipe
if (newOffsetX > maxOffsetX) {
newOffsetX = maxOffsetX;
}
Expand All @@ -41,25 +105,30 @@ export function useSwiper() {
};

const onTouchEnd = () => {
const currentOffsetX = currentOffsetXRef.current;
const containerWidth = containerWidthRef.current;
let newOffsetX = offsetXRef.current!;
const diff = currentOffsetX - newOffsetX;
const swipeDiff = initialOffsetX - newOffsetX;

const containerEl = containerRef.current!;
const containerWidth = containerEl.offsetWidth;

let indexToMove: number;

// we need to check difference in absolute/positive value (if diff is more than 40px)
if (Math.abs(diff) > MIN_SWIPE_REQUIRED) {
if (diff > 0) {
// swipe to the right if diff is positive
newOffsetX = Math.floor(newOffsetX / containerWidth) * containerWidth;
// check difference is bigger than MIN_SWIPE_REQUIRED
if (Math.abs(swipeDiff) > MIN_SWIPE_REQUIRED) {
if (swipeDiff > 0) {
// move to the right slide
indexToMove = Math.floor(newOffsetX / containerWidth);
} else {
// swipe to the left if diff is negative
newOffsetX = Math.ceil(newOffsetX / containerWidth) * containerWidth;
// move to the left slide
indexToMove = Math.ceil(newOffsetX / containerWidth);
}
} else {
// remain in the current image
newOffsetX = Math.round(newOffsetX / containerWidth) * containerWidth;
// remain in the current slide
indexToMove = Math.round(newOffsetX / containerWidth);
}

newOffsetX = indexToMove * containerWidth;

setIsSwiping(false);
setOffsetX(newOffsetX);
setCurrentIdx(Math.abs(newOffsetX / containerWidth));
Expand All @@ -70,39 +139,9 @@ export function useSwiper() {
window.removeEventListener('mousemove', onTouchMove);
};

const onTouchStart = (e: React.TouchEvent<HTMLDivElement> | React.MouseEvent<HTMLDivElement>) => {
setIsSwiping(true);

currentOffsetXRef.current = offsetXRef.current!;
startXRef.current = getTouchEventData(e).clientX;

const containerEl = containerRef.current;

if (!containerEl) {
return;
}

const containerWidth = containerEl.offsetWidth;

containerWidthRef.current = containerWidth;
minOffsetXRef.current = containerWidth - containerEl.scrollWidth;

window.addEventListener('touchmove', onTouchMove);
window.addEventListener('touchend', onTouchEnd);
window.addEventListener('mousemove', onTouchMove);
window.addEventListener('mouseup', onTouchEnd);
};

const onIndicatorClick = (idx: number) => {
const containerEl = containerRef.current;
if (!containerEl) {
return;
}

const containerWidth = containerEl.offsetWidth;

setCurrentIdx(idx);
setOffsetX(-(containerWidth * idx));
setOffsetX(-(containerWidthRef.current * idx));
};

return {
Expand Down

0 comments on commit 0e0f649

Please sign in to comment.