react-native-quick-scroll
Version:
Customizable and performant React Native scroll bar component for quickly scrolling through large lists (based on FlatList)
162 lines (148 loc) • 4.57 kB
JavaScript
import React from 'react';
import { View, FlatList, Animated, Dimensions } from 'react-native';
import { PanGestureHandler } from 'react-native-gesture-handler';
const ScreenWidth = Dimensions.get('window').width;
class QuickScrollList extends React.Component {
static defaultProps = {
flashDuration: 40,
flashOutDuration: 2000,
rightOffset: 10,
thumbHeight: 60,
hiddenPosition: ScreenWidth + 10,
touchAreaWidth: 25,
thumbStyle: {},
scrollbarStyle: {},
containerStyle: {}
};
position = new Animated.Value(0);
scrollBar = new Animated.Value(ScreenWidth);
flatlistRef = React.createRef();
disableOnScrollEvent = false;
createRef = (ref) => {
this.flatlistRef = ref;
this.props.ref && this.props.ref(ref);
};
onThumbDrag = (event) => {
const { data, itemHeight, thumbHeight, viewportHeight } = this.props;
const availableHeight = viewportHeight - thumbHeight;
const positionY = this.position.__getValue();
const gestureY = event.nativeEvent.absoluteY;
if (gestureY >= 0 && gestureY <= availableHeight) {
this.disableOnScrollEvent = true;
const thumbPos = (positionY / (viewportHeight - thumbHeight)).toFixed(3);
let lastIndex = data.length - Math.floor(viewportHeight / itemHeight) + 1;
let index = Math.floor(lastIndex * thumbPos);
if (index > lastIndex) index = lastIndex;
if (index < 0) index = 0;
Animated.event([{ nativeEvent: { absoluteY: this.position } }])(event);
this.flatlistRef.scrollToIndex({
index,
viewPosition: 0,
animated: true
});
}
};
moveThumbOnScroll = (e) => {
if (this.disableOnScrollEvent) {
this.disableOnScrollEvent = false;
return;
}
const { itemHeight, data, thumbHeight, viewportHeight } = this.props;
const listHeight = data.length * itemHeight;
const endPosition = listHeight - viewportHeight;
const offsetY = e.nativeEvent.contentOffset.y;
const diff = (viewportHeight - thumbHeight) / endPosition;
this.position.setValue(offsetY * diff);
};
flashScrollBar = () => {
const { flashDuration, rightOffset } = this.props;
Animated.timing(this.scrollBar, {
toValue: ScreenWidth - rightOffset,
duration: flashDuration,
useNativeDriver: true
}).start();
};
onScroll = (event, gesture) => {
this.flashScrollBar();
this.moveThumbOnScroll(event);
this.props.onScroll && this.props.onScroll(event, gesture);
};
onScrollGlideEnd = (event, gesture) => {
const { flashDuration, flashOutDuration } = this.props;
const flashOut = Animated.timing(this.scrollBar, {
toValue: this.props.hiddenPosition,
duration: flashDuration,
useNativeDriver: true
});
setTimeout(() => flashOut.start(), flashOutDuration);
this.props.onMomentumScrollEnd && this.props.onMomentumScrollEnd(event, gesture);
};
convertStyle(prop) {
if (Array.isArray(prop)) {
let propObj = {};
prop.forEach((val) => {
propObj = { ...propObj, ...val };
});
return propObj;
}
return prop;
}
render() {
//prettier-ignore
const { thumbHeight, thumbStyle, scrollbarStyle, containerStyle, viewportHeight, touchAreaWidth } = this.props;
const rightOffset = { transform: [{ translateX: this.scrollBar }] };
const thumbTransform = { transform: [{ translateY: this.position }] };
return (
<View style={[styles.mainWrapper, this.convertStyle(containerStyle)]}>
<FlatList
{...this.props}
ref={this.createRef}
onScroll={this.onScroll}
onScrollEndDrag={this.onScrollEnd}
onMomentumScrollEnd={this.onScrollGlideEnd}
showsVerticalScrollIndicator={false}
onScrollToIndexFailed={() => {}}
/>
<Animated.View
style={[
styles.scrollBar,
rightOffset,
{ height: viewportHeight },
this.convertStyle(scrollbarStyle)
]}>
<PanGestureHandler
onGestureEvent={this.onThumbDrag}
hitSlop={{ left: touchAreaWidth }}
maxPointers={1}>
<Animated.View
style={[
styles.thumb,
thumbTransform,
{ height: thumbHeight },
this.convertStyle(thumbStyle)
]}
/>
</PanGestureHandler>
</Animated.View>
</View>
);
}
}
export default QuickScrollList;
const styles = {
mainWrapper: {
flex: 1
},
scrollBar: {
position: 'absolute',
width: 10,
backgroundColor: 'transparent',
alignItems: 'center'
},
thumb: {
width: 4,
borderRadius: 4,
backgroundColor: '#4C4C4C',
elevation: 2
}
};