6
} from 'react';
7
8
import ScrollTimelinePolyfill from 'animation-timelines/scroll-timeline';
9
+import TouchPanTimeline from 'animation-timelines/touch-pan-timeline';
10
+
11
+const ua = typeof navigator === 'undefined' ? '' : navigator.userAgent;
12
+const isSafariMobile =
13
+ ua.indexOf('Safari') !== -1 &&
14
+ (ua.indexOf('iPhone') !== -1 ||
15
+ ua.indexOf('iPad') !== -1 ||
16
+ ua.indexOf('iPod') !== -1);
17
18
// Example of a Component that can recognize swipe gestures using a ScrollTimeline
19
// without scrolling its own content. Allowing it to be used as an inert gesture
31
32
const scrollRef = useRef(null);
33
const activeGesture = useRef(null);
34
+ const touchTimeline = useRef(null);
35
+
36
+ function onTouchStart(event) {
37
+ if (!isSafariMobile && typeof ScrollTimeline === 'function') {
38
+ // If not Safari and native ScrollTimeline is supported, then we use that.
39
+ return;
40
+ }
41
+ if (touchTimeline.current) {
42
+ // We can catch the gesture before it settles.
43
+ return;
44
+ }
45
+ const scrollElement = scrollRef.current;
46
+ const bounds =
47
+ axis === 'x' ? scrollElement.clientWidth : scrollElement.clientHeight;
48
+ const range =
49
+ direction === 'left' || direction === 'up' ? [bounds, 0] : [0, -bounds];
50
+ const timeline = new TouchPanTimeline({
51
+ touch: event,
52
+ source: scrollElement,
53
+ axis: axis,
54
+ range: range,
55
+ snap: range,
56
+ });
57
+ touchTimeline.current = timeline;
58
+ timeline.settled.then(() => {
59
+ if (touchTimeline.current !== timeline) {
60
+ return;
61
+ }
62
+ touchTimeline.current = null;
63
+ const changed =
64
+ direction === 'left' || direction === 'up'
65
+ ? timeline.currentTime < 50
66
+ : timeline.currentTime > 50;
67
+ onGestureEnd(changed);
68
+ });
69
+ }
70
+
71
+ function onTouchEnd() {
72
+ if (activeGesture.current === null) {
73
+ // If we didn't start a gesture before we release, we can release our
74
+ // timeline.
75
+ touchTimeline.current = null;
76
+ }
77
+ }
78
+
79
function onScroll() {
80
if (activeGesture.current !== null) {
81
return;
82
}
83
84
let scrollTimeline;
32
- if (typeof ScrollTimeline === 'function') {
85
+ if (touchTimeline.current) {
86
+ // We're in a polyfilled touch gesture. Let's use that timeline instead.
87
+ scrollTimeline = touchTimeline.current;
88
+ } else if (typeof ScrollTimeline === 'function') {
89
// eslint-disable-next-line no-undef
90
scrollTimeline = new ScrollTimeline({
91
source: scrollRef.current,
113
}
114
);
115
}
116
+ function onGestureEnd(changed) {
117
+ // Reset scroll
118
+ if (changed) {
119
+ // Trigger side-effects
120
+ startTransition(action);
121
+ }
122
+ if (activeGesture.current !== null) {
123
+ const cancelGesture = activeGesture.current;
124
+ activeGesture.current = null;
125
+ cancelGesture();
126
+ }
127
+ }
128
function onScrollEnd() {
129
+ if (touchTimeline.current) {
130
+ // We have a touch gesture controlling the swipe.
131
+ return;
132
+ }
133
let changed;
134
const scrollElement = scrollRef.current;
135
if (axis === 'x') {
147
? scrollElement.scrollTop < halfway
148
: scrollElement.scrollTop > halfway;
149
}
78
- // Reset scroll
79
- if (changed) {
80
- // Trigger side-effects
81
- startTransition(action);
82
- }
83
- if (activeGesture.current !== null) {
84
- const cancelGesture = activeGesture.current;
85
- activeGesture.current = null;
86
- cancelGesture();
87
- }
150
+ onGestureEnd(changed);
151
}
152
153
useEffect(() => {
239
return (
240
<div
241
style={scrollStyle}
242
+ onTouchStart={onTouchStart}
243
+ onTouchEnd={onTouchEnd}
244
+ onTouchCancel={onTouchEnd}
245
onScroll={onScroll}
246
onScrollEnd={onScrollEnd}
247
ref={scrollRef}>