@@ -13,6 +13,7 @@ import View from '../View';
13
13
import ViewPropTypes from '../ViewPropTypes' ;
14
14
import React , { Component } from 'react' ;
15
15
import { bool , func , number } from 'prop-types' ;
16
+ import findNodeHandle from '../findNodeHandle' ;
16
17
17
18
const normalizeScrollEvent = e => ( {
18
19
nativeEvent : {
@@ -44,6 +45,37 @@ const normalizeScrollEvent = e => ({
44
45
timeStamp : Date . now ( )
45
46
} ) ;
46
47
48
+ const normalizeWindowScrollEvent = e => ( {
49
+ nativeEvent : {
50
+ contentOffset : {
51
+ get x ( ) {
52
+ return window . scrollX ;
53
+ } ,
54
+ get y ( ) {
55
+ return window . scrollY ;
56
+ }
57
+ } ,
58
+ contentSize : {
59
+ get height ( ) {
60
+ return window . innerHeight ;
61
+ } ,
62
+ get width ( ) {
63
+ return window . innerWidth ;
64
+ }
65
+ } ,
66
+ layoutMeasurement : {
67
+ get height ( ) {
68
+ // outer dimensions do not apply for windows
69
+ return window . innerHeight ;
70
+ } ,
71
+ get width ( ) {
72
+ return window . innerWidth ;
73
+ }
74
+ }
75
+ } ,
76
+ timeStamp : Date . now ( )
77
+ } ) ;
78
+
47
79
/**
48
80
* Encapsulates the Web-specific scroll throttling and disabling logic
49
81
*/
@@ -63,23 +95,115 @@ export default class ScrollViewBase extends Component<*> {
63
95
scrollEnabled : bool ,
64
96
scrollEventThrottle : number ,
65
97
showsHorizontalScrollIndicator : bool ,
66
- showsVerticalScrollIndicator : bool
98
+ showsVerticalScrollIndicator : bool ,
99
+ useWindowScrolling : bool
67
100
} ;
68
101
69
102
static defaultProps = {
70
103
scrollEnabled : true ,
71
- scrollEventThrottle : 0
104
+ scrollEventThrottle : 0 ,
105
+ useWindowScrolling : false
72
106
} ;
73
107
74
108
_debouncedOnScrollEnd = debounce ( this . _handleScrollEnd , 100 ) ;
75
109
_state = { isScrolling : false , scrollLastTick : 0 } ;
110
+ _windowResizeObserver : any | null = null ;
76
111
77
112
setNativeProps ( props : Object ) {
78
113
if ( this . _viewRef ) {
79
114
this . _viewRef . setNativeProps ( props ) ;
80
115
}
81
116
}
82
117
118
+ _handleWindowLayout = ( ) => {
119
+ const { onLayout } = this . props ;
120
+
121
+ if ( typeof onLayout === 'function' ) {
122
+ const layout = {
123
+ x : 0 ,
124
+ y : 0 ,
125
+ get width ( ) {
126
+ return window . innerWidth ;
127
+ } ,
128
+ get height ( ) {
129
+ return window . innerHeight ;
130
+ }
131
+ } ;
132
+
133
+ const nativeEvent = {
134
+ layout
135
+ } ;
136
+
137
+ // $FlowFixMe
138
+ Object . defineProperty ( nativeEvent , 'target' , {
139
+ enumerable : true ,
140
+ get : ( ) => findNodeHandle ( this )
141
+ } ) ;
142
+
143
+ onLayout ( {
144
+ nativeEvent,
145
+ timeStamp : Date . now ( )
146
+ } ) ;
147
+ }
148
+ } ;
149
+
150
+ registerWindowHandlers ( ) {
151
+ window . addEventListener ( 'scroll' , this . _handleScroll ) ;
152
+ window . addEventListener ( 'touchmove' , this . _handleWindowTouchMove ) ;
153
+ window . addEventListener ( 'wheel' , this . _handleWindowWheel ) ;
154
+ window . addEventListener ( 'resize' , this . _handleWindowLayout ) ;
155
+
156
+ if ( typeof window . ResizeObserver === 'function' ) {
157
+ this . _windowResizeObserver = new window . ResizeObserver ( ( /*entries*/ ) => {
158
+ this . _handleWindowLayout ( ) ;
159
+ } ) ;
160
+ // handle changes of the window content size.
161
+ // It technically works with regular onLayout of the container,
162
+ // but this called very often if the content change based on scrolling, e.g. FlatList
163
+ this . _windowResizeObserver . observe ( window . document . body ) ;
164
+ } else if ( process . env . NODE_ENV !== 'production' && process . env . NODE_ENV !== 'test' ) {
165
+ console . warn (
166
+ '"useWindowScrolling" relies on ResizeObserver which is not supported by your browser. ' +
167
+ 'Please include a polyfill, e.g., https://github.com/que-etc/resize-observer-polyfill. ' +
168
+ 'Only handling the window.onresize event.'
169
+ ) ;
170
+ }
171
+ this . _handleWindowLayout ( ) ;
172
+ }
173
+
174
+ unregisterWindowHandlers ( ) {
175
+ window . removeEventListener ( 'scroll' , this . _handleScroll ) ;
176
+ window . removeEventListener ( 'touchmove' , this . _handleWindowTouchMove ) ;
177
+ window . removeEventListener ( 'wheel' , this . _handleWindowWheel ) ;
178
+ const { _windowResizeObserver } = this ;
179
+ if ( _windowResizeObserver ) {
180
+ _windowResizeObserver . disconnect ( ) ;
181
+ }
182
+ }
183
+
184
+ componentDidMount ( ) {
185
+ if ( this . props . useWindowScrolling ) {
186
+ this . registerWindowHandlers ( ) ;
187
+ }
188
+ }
189
+
190
+ componentDidUpdate ( { useWindowScrolling : wasUsingBodyScroll } : Object ) {
191
+ const { useWindowScrolling } = this . props ;
192
+ if ( wasUsingBodyScroll !== useWindowScrolling ) {
193
+ if ( wasUsingBodyScroll ) {
194
+ this . unregisterWindowHandlers ( ) ;
195
+ } else {
196
+ this . registerWindowHandlers ( ) ;
197
+ }
198
+ }
199
+ }
200
+
201
+ componentWillUnmount ( ) {
202
+ if ( this . props . useWindowScrolling ) {
203
+ this . unregisterWindowHandlers ( ) ;
204
+ }
205
+ }
206
+
83
207
render ( ) {
84
208
const {
85
209
scrollEnabled,
@@ -118,6 +242,7 @@ export default class ScrollViewBase extends Component<*> {
118
242
snapToInterval,
119
243
snapToAlignment,
120
244
zoomScale,
245
+ useWindowScrolling,
121
246
/* eslint-enable */
122
247
...other
123
248
} = this . props ;
@@ -127,9 +252,17 @@ export default class ScrollViewBase extends Component<*> {
127
252
return (
128
253
< View
129
254
{ ...other }
130
- onScroll = { this . _handleScroll }
131
- onTouchMove = { this . _createPreventableScrollHandler ( this . props . onTouchMove ) }
132
- onWheel = { this . _createPreventableScrollHandler ( this . props . onWheel ) }
255
+ onLayout = { useWindowScrolling ? undefined : other . onLayout }
256
+ // disable regular scroll handling if window scrolling is used
257
+ onScroll = { useWindowScrolling ? undefined : this . _handleScroll }
258
+ onTouchMove = {
259
+ useWindowScrolling
260
+ ? undefined
261
+ : this . _createPreventableScrollHandler ( this . props . onTouchMove )
262
+ }
263
+ onWheel = {
264
+ useWindowScrolling ? undefined : this . _createPreventableScrollHandler ( this . props . onWheel )
265
+ }
133
266
ref = { this . _setViewRef }
134
267
style = { [
135
268
style ,
@@ -153,8 +286,26 @@ export default class ScrollViewBase extends Component<*> {
153
286
} ;
154
287
} ;
155
288
289
+ _handleWindowTouchMove = this . _createPreventableScrollHandler ( ( ) => {
290
+ const { onTouchMove } = this . props ;
291
+ if ( typeof onTouchMove === 'function' ) {
292
+ return onTouchMove ( ) ;
293
+ }
294
+ } ) ;
295
+
296
+ _handleWindowWheel = this . _createPreventableScrollHandler ( ( ) => {
297
+ const { onWheel } = this . props ;
298
+ if ( typeof onWheel === 'function' ) {
299
+ return onWheel ( ) ;
300
+ }
301
+ } ) ;
302
+
156
303
_handleScroll = ( e : Object ) => {
157
- e . persist ( ) ;
304
+ if ( typeof e . persist === 'function' ) {
305
+ // this is a react SyntheticEvent, but not for window scrolling
306
+ e . persist ( ) ;
307
+ }
308
+
158
309
e . stopPropagation ( ) ;
159
310
const { scrollEventThrottle } = this . props ;
160
311
// A scroll happened, so the scroll bumps the debounce.
@@ -176,18 +327,20 @@ export default class ScrollViewBase extends Component<*> {
176
327
}
177
328
178
329
_handleScrollTick ( e : Object ) {
179
- const { onScroll } = this . props ;
330
+ const { onScroll, useWindowScrolling } = this . props ;
180
331
this . _state . scrollLastTick = Date . now ( ) ;
181
332
if ( onScroll ) {
182
- onScroll ( normalizeScrollEvent ( e ) ) ;
333
+ const transformEvent = useWindowScrolling ? normalizeWindowScrollEvent : normalizeScrollEvent ;
334
+ onScroll ( transformEvent ( e ) ) ;
183
335
}
184
336
}
185
337
186
338
_handleScrollEnd ( e : Object ) {
187
- const { onScroll } = this . props ;
339
+ const { onScroll, useWindowScrolling } = this . props ;
188
340
this . _state . isScrolling = false ;
189
341
if ( onScroll ) {
190
- onScroll ( normalizeScrollEvent ( e ) ) ;
342
+ const transformEvent = useWindowScrolling ? normalizeWindowScrollEvent : normalizeScrollEvent ;
343
+ onScroll ( transformEvent ( e ) ) ;
191
344
}
192
345
}
193
346
0 commit comments