-
Notifications
You must be signed in to change notification settings - Fork 169
Expand file tree
/
Copy pathuseMobileTouchMove.ts
More file actions
104 lines (87 loc) · 2.91 KB
/
useMobileTouchMove.ts
File metadata and controls
104 lines (87 loc) · 2.91 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
import useLayoutEffect from '@rc-component/util/lib/hooks/useLayoutEffect';
import type * as React from 'react';
import { useRef } from 'react';
const SMOOTH_PTG = 14 / 15;
export default function useMobileTouchMove(
inVirtual: boolean,
listRef: React.RefObject<HTMLDivElement>,
callback: (
isHorizontal: boolean,
offset: number,
smoothOffset: boolean,
e?: TouchEvent,
) => boolean,
) {
const touchedRef = useRef(false);
const touchXRef = useRef(0);
const touchYRef = useRef(0);
const elementRef = useRef<HTMLElement>(null);
// Smooth scroll
const intervalRef = useRef(null);
/* eslint-disable prefer-const */
let cleanUpEvents: () => void;
const onTouchMove = (e: TouchEvent) => {
if (touchedRef.current) {
const currentX = Math.ceil(e.touches[0].pageX);
const currentY = Math.ceil(e.touches[0].pageY);
let offsetX = touchXRef.current - currentX;
let offsetY = touchYRef.current - currentY;
const isHorizontal = Math.abs(offsetX) > Math.abs(offsetY);
if (isHorizontal) {
touchXRef.current = currentX;
} else {
touchYRef.current = currentY;
}
const scrollHandled = callback(isHorizontal, isHorizontal ? offsetX : offsetY, false, e);
if (scrollHandled) {
e.preventDefault();
}
// Smooth interval
clearInterval(intervalRef.current);
if (scrollHandled) {
intervalRef.current = setInterval(() => {
if (isHorizontal) {
offsetX *= SMOOTH_PTG;
} else {
offsetY *= SMOOTH_PTG;
}
const offset = Math.floor(isHorizontal ? offsetX : offsetY);
if (!callback(isHorizontal, offset, true) || Math.abs(offset) <= 0.1) {
clearInterval(intervalRef.current);
}
}, 16);
}
}
};
const onTouchEnd = () => {
touchedRef.current = false;
cleanUpEvents();
};
const onTouchStart = (e: TouchEvent) => {
cleanUpEvents();
if (e.touches.length === 1 && !touchedRef.current) {
touchedRef.current = true;
touchXRef.current = Math.ceil(e.touches[0].pageX);
touchYRef.current = Math.ceil(e.touches[0].pageY);
elementRef.current = e.target as HTMLElement;
elementRef.current.addEventListener('touchmove', onTouchMove, { passive: false });
elementRef.current.addEventListener('touchend', onTouchEnd, { passive: true });
}
};
cleanUpEvents = () => {
if (elementRef.current) {
elementRef.current.removeEventListener('touchmove', onTouchMove);
elementRef.current.removeEventListener('touchend', onTouchEnd);
}
};
useLayoutEffect(() => {
if (inVirtual) {
listRef.current.addEventListener('touchstart', onTouchStart, { passive: true });
}
return () => {
listRef.current?.removeEventListener('touchstart', onTouchStart);
cleanUpEvents();
clearInterval(intervalRef.current);
};
}, [inVirtual]);
}