@@ -13,6 +13,7 @@ import View from '../View';
1313import ViewPropTypes from '../ViewPropTypes' ;
1414import React , { Component } from 'react' ;
1515import { bool , func , number } from 'prop-types' ;
16+ import findNodeHandle from '../findNodeHandle' ;
1617
1718const normalizeScrollEvent = e => ( {
1819 nativeEvent : {
@@ -44,6 +45,37 @@ const normalizeScrollEvent = e => ({
4445 timeStamp : Date . now ( )
4546} ) ;
4647
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+
4779/**
4880 * Encapsulates the Web-specific scroll throttling and disabling logic
4981 */
@@ -63,23 +95,115 @@ export default class ScrollViewBase extends Component<*> {
6395 scrollEnabled : bool ,
6496 scrollEventThrottle : number ,
6597 showsHorizontalScrollIndicator : bool ,
66- showsVerticalScrollIndicator : bool
98+ showsVerticalScrollIndicator : bool ,
99+ useWindowScrolling : bool
67100 } ;
68101
69102 static defaultProps = {
70103 scrollEnabled : true ,
71- scrollEventThrottle : 0
104+ scrollEventThrottle : 0 ,
105+ useWindowScrolling : false
72106 } ;
73107
74108 _debouncedOnScrollEnd = debounce ( this . _handleScrollEnd , 100 ) ;
75109 _state = { isScrolling : false , scrollLastTick : 0 } ;
110+ _windowResizeObserver : any | null = null ;
76111
77112 setNativeProps ( props : Object ) {
78113 if ( this . _viewRef ) {
79114 this . _viewRef . setNativeProps ( props ) ;
80115 }
81116 }
82117
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+
83207 render ( ) {
84208 const {
85209 scrollEnabled,
@@ -118,6 +242,7 @@ export default class ScrollViewBase extends Component<*> {
118242 snapToInterval,
119243 snapToAlignment,
120244 zoomScale,
245+ useWindowScrolling,
121246 /* eslint-enable */
122247 ...other
123248 } = this . props ;
@@ -127,9 +252,17 @@ export default class ScrollViewBase extends Component<*> {
127252 return (
128253 < View
129254 { ...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+ }
133266 ref = { this . _setViewRef }
134267 style = { [
135268 style ,
@@ -153,8 +286,26 @@ export default class ScrollViewBase extends Component<*> {
153286 } ;
154287 } ;
155288
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+
156303 _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+
158309 e . stopPropagation ( ) ;
159310 const { scrollEventThrottle } = this . props ;
160311 // A scroll happened, so the scroll bumps the debounce.
@@ -176,18 +327,20 @@ export default class ScrollViewBase extends Component<*> {
176327 }
177328
178329 _handleScrollTick ( e : Object ) {
179- const { onScroll } = this . props ;
330+ const { onScroll, useWindowScrolling } = this . props ;
180331 this . _state . scrollLastTick = Date . now ( ) ;
181332 if ( onScroll ) {
182- onScroll ( normalizeScrollEvent ( e ) ) ;
333+ const transformEvent = useWindowScrolling ? normalizeWindowScrollEvent : normalizeScrollEvent ;
334+ onScroll ( transformEvent ( e ) ) ;
183335 }
184336 }
185337
186338 _handleScrollEnd ( e : Object ) {
187- const { onScroll } = this . props ;
339+ const { onScroll, useWindowScrolling } = this . props ;
188340 this . _state . isScrolling = false ;
189341 if ( onScroll ) {
190- onScroll ( normalizeScrollEvent ( e ) ) ;
342+ const transformEvent = useWindowScrolling ? normalizeWindowScrollEvent : normalizeScrollEvent ;
343+ onScroll ( transformEvent ( e ) ) ;
191344 }
192345 }
193346
0 commit comments