@@ -6,6 +6,14 @@ import React, {
6
6
} from 'react' ;
7
7
8
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 ) ;
9
17
10
18
// Example of a Component that can recognize swipe gestures using a ScrollTimeline
11
19
// without scrolling its own content. Allowing it to be used as an inert gesture
@@ -23,13 +31,61 @@ export default function SwipeRecognizer({
23
31
24
32
const scrollRef = useRef ( null ) ;
25
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
+
26
79
function onScroll ( ) {
27
80
if ( activeGesture . current !== null ) {
28
81
return ;
29
82
}
30
83
31
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' ) {
33
89
// eslint-disable-next-line no-undef
34
90
scrollTimeline = new ScrollTimeline ( {
35
91
source : scrollRef . current ,
@@ -57,7 +113,23 @@ export default function SwipeRecognizer({
57
113
}
58
114
) ;
59
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
+ }
60
128
function onScrollEnd ( ) {
129
+ if ( touchTimeline . current ) {
130
+ // We have a touch gesture controlling the swipe.
131
+ return ;
132
+ }
61
133
let changed ;
62
134
const scrollElement = scrollRef . current ;
63
135
if ( axis === 'x' ) {
@@ -75,16 +147,7 @@ export default function SwipeRecognizer({
75
147
? scrollElement . scrollTop < halfway
76
148
: scrollElement . scrollTop > halfway ;
77
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 ) ;
88
151
}
89
152
90
153
useEffect ( ( ) => {
@@ -176,6 +239,9 @@ export default function SwipeRecognizer({
176
239
return (
177
240
< div
178
241
style = { scrollStyle }
242
+ onTouchStart = { onTouchStart }
243
+ onTouchEnd = { onTouchEnd }
244
+ onTouchCancel = { onTouchEnd }
179
245
onScroll = { onScroll }
180
246
onScrollEnd = { onScrollEnd }
181
247
ref = { scrollRef } >
0 commit comments