[fix] ScrollView to use 'touch-action' to disable scroll

The recommendation from the Chrome team is to use 'touch-action' to
disable scrolling (via touch modality) when passive event listeners are
in use.

Close #563
This commit is contained in:
Nicolas Gallagher
2017-07-13 17:10:38 -07:00
parent 3677f0dd57
commit faec2b4a83
4 changed files with 40 additions and 38 deletions
@@ -5,7 +5,7 @@
*/ */
import React, { PureComponent } from 'react'; import React, { PureComponent } from 'react';
import { Button, ScrollView, StyleSheet, Text, View } from 'react-native'; import { Button, ScrollView, StyleSheet, Text, TouchableHighlight, View } from 'react-native';
export default class ScrollToExample extends PureComponent { export default class ScrollToExample extends PureComponent {
render() { render() {
@@ -20,11 +20,15 @@ export default class ScrollToExample extends PureComponent {
style={styles.scrollViewStyle} style={styles.scrollViewStyle}
> >
{Array.from({ length: 50 }).map((item, i) => {Array.from({ length: 50 }).map((item, i) =>
<View key={i} style={[styles.box, styles.horizontalBox]}> <TouchableHighlight
key={i}
onPress={() => {}}
style={[styles.box, styles.horizontalBox]}
>
<Text> <Text>
{i} {i}
</Text> </Text>
</View> </TouchableHighlight>
)} )}
</ScrollView> </ScrollView>
<Button <Button
@@ -671,6 +671,11 @@ const stylePropTypes = [
name: 'shadowRadius', name: 'shadowRadius',
typeInfo: 'number | string' typeInfo: 'number | string'
}, },
{
label: 'web',
name: 'touchAction',
typeInfo: 'string'
},
{ {
name: 'top', name: 'top',
typeInfo: 'number | string' typeInfo: 'number | string'
+27 -35
View File
@@ -9,7 +9,7 @@
*/ */
import debounce from 'debounce'; import debounce from 'debounce';
import findNodeHandle from '../../modules/findNodeHandle'; import StyleSheet from '../../apis/StyleSheet';
import View from '../View'; import View from '../View';
import ViewPropTypes from '../View/ViewPropTypes'; import ViewPropTypes from '../View/ViewPropTypes';
import React, { Component } from 'react'; import React, { Component } from 'react';
@@ -72,41 +72,22 @@ export default class ScrollViewBase extends Component {
_debouncedOnScrollEnd = debounce(this._handleScrollEnd, 100); _debouncedOnScrollEnd = debounce(this._handleScrollEnd, 100);
_state = { isScrolling: false, scrollLastTick: 0 }; _state = { isScrolling: false, scrollLastTick: 0 };
_node = null;
componentDidMount() {
this._node && this._node.addEventListener('scroll', this._handleScroll);
this._node && this._node.addEventListener('touchmove', this._handlePreventableTouchMove);
this._node && this._node.addEventListener('wheel', this._handlePreventableWheel);
}
componentWillUnmount() {
this._node && this._node.removeEventListener('scroll', this._handleScroll);
this._node && this._node.removeEventListener('touchmove', this._handlePreventableTouchMove);
this._node && this._node.removeEventListener('wheel', this._handlePreventableWheel);
}
_createPreventableScrollHandler = (handler: Function) => { _createPreventableScrollHandler = (handler: Function) => {
return (e: Object) => { return (e: Object) => {
if (!this.props.scrollEnabled) { if (this.props.scrollEnabled) {
e.preventDefault();
} else {
if (handler) { if (handler) {
handler(e); handler(e);
} }
} else {
// To disable scrolling in all browsers except Chrome
e.preventDefault();
} }
}; };
}; };
_handlePreventableTouchMove = (e: Object) => { _handleScroll = (e: SyntheticEvent) => {
this._createPreventableScrollHandler(this.props.onTouchMove)(e); e.persist();
};
_handlePreventableWheel = (e: Object) => {
this._createPreventableScrollHandler(this.props.onWheel)(e);
};
_handleScroll = (e: Object) => {
e.stopPropagation(); e.stopPropagation();
const { scrollEventThrottle } = this.props; const { scrollEventThrottle } = this.props;
// A scroll happened, so the scroll bumps the debounce. // A scroll happened, so the scroll bumps the debounce.
@@ -143,10 +124,6 @@ export default class ScrollViewBase extends Component {
} }
} }
_setNodeRef = (element: View) => {
this._node = findNodeHandle(element);
};
_shouldEmitScrollEvent(lastTick: number, eventThrottle: number) { _shouldEmitScrollEvent(lastTick: number, eventThrottle: number) {
const timeSinceLastTick = Date.now() - lastTick; const timeSinceLastTick = Date.now() - lastTick;
return eventThrottle > 0 && timeSinceLastTick >= eventThrottle; return eventThrottle > 0 && timeSinceLastTick >= eventThrottle;
@@ -154,16 +131,14 @@ export default class ScrollViewBase extends Component {
render() { render() {
const { const {
scrollEnabled,
style,
/* eslint-disable */ /* eslint-disable */
onMomentumScrollBegin, onMomentumScrollBegin,
onMomentumScrollEnd, onMomentumScrollEnd,
onScroll,
onScrollBeginDrag, onScrollBeginDrag,
onScrollEndDrag, onScrollEndDrag,
onTouchMove,
onWheel,
removeClippedSubviews, removeClippedSubviews,
scrollEnabled,
scrollEventThrottle, scrollEventThrottle,
showsHorizontalScrollIndicator, showsHorizontalScrollIndicator,
showsVerticalScrollIndicator, showsVerticalScrollIndicator,
@@ -171,6 +146,23 @@ export default class ScrollViewBase extends Component {
...other ...other
} = this.props; } = this.props;
return <View {...other} ref={this._setNodeRef} />; return (
<View
{...other}
onScroll={this._handleScroll}
onTouchMove={this._createPreventableScrollHandler(this.props.onTouchMove)}
onWheel={this._createPreventableScrollHandler(this.props.onWheel)}
style={[style, !scrollEnabled && styles.scrollDisabled]}
/>
);
} }
} }
// Chrome doesn't support e.preventDefault in this case; touch-action must be
// used to disable scrolling.
// https://developers.google.com/web/updates/2017/01/scrolling-intervention
const styles = StyleSheet.create({
scrollDisabled: {
touchAction: 'none'
}
});
@@ -48,6 +48,7 @@ const ViewStylePropTypes = {
outlineColor: ColorPropType, outlineColor: ColorPropType,
perspective: oneOfType([number, string]), perspective: oneOfType([number, string]),
perspectiveOrigin: string, perspectiveOrigin: string,
touchAction: string,
transitionDelay: string, transitionDelay: string,
transitionDuration: string, transitionDuration: string,
transitionProperty: string, transitionProperty: string,