├── .gitignore ├── .npmignore ├── LICENSE.txt ├── README.md ├── demo └── rec.gif ├── index.js ├── lib └── QuickScrollList.js └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | # OSX 2 | # 3 | .DS_Store 4 | 5 | # Xcode 6 | # 7 | build/ 8 | *.pbxuser 9 | !default.pbxuser 10 | *.mode1v3 11 | !default.mode1v3 12 | *.mode2v3 13 | !default.mode2v3 14 | *.perspectivev3 15 | !default.perspectivev3 16 | xcuserdata 17 | *.xccheckout 18 | *.moved-aside 19 | DerivedData 20 | *.hmap 21 | *.ipa 22 | *.xcuserstate 23 | project.xcworkspace 24 | 25 | # Android/IntelliJ 26 | # 27 | build/ 28 | .idea 29 | .gradle 30 | local.properties 31 | *.iml 32 | 33 | # node.js 34 | # 35 | node_modules/ 36 | npm-debug.log 37 | yarn-error.log 38 | 39 | # BUCK 40 | buck-out/ 41 | \.buckd/ 42 | *.keystore 43 | 44 | # fastlane 45 | # 46 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 47 | # screenshots whenever they are needed. 48 | # For more information about the recommended setup visit: 49 | # https://docs.fastlane.tools/best-practices/source-control/ 50 | 51 | */fastlane/report.xml 52 | */fastlane/Preview.html 53 | */fastlane/screenshots 54 | 55 | # Bundle artifact 56 | *.jsbundle 57 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | demo/ 3 | npm-debug.log 4 | yarn-error.log 5 | 6 | # BUCK 7 | buck-out/ 8 | \.buckd/ 9 | *.keystore -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Faisal Arshed 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-native-quick-scroll [![npm version](https://img.shields.io/npm/v/react-native-quick-scroll.svg?style=flat)](https://www.npmjs.com/package/react-native-quick-scroll) 2 | 3 | Customizable and performant React Native scroll bar component for quickly scrolling through large lists (based on FlatList) 4 | 5 | ## Demo 6 | 7 | ![Demo gif](https://github.com/farshed/react-native-quick-scroll/blob/master/demo/rec.gif?raw=true) 8 | 9 | ## Installation 10 | 11 | react-native-quick-scroll uses react-native-gesture-handler to handle pan gestures. Follow these guides to install react-native-gesture-handler in your project. 12 | 13 | - [Expo](https://docs.expo.io/versions/latest/sdk/gesture-handler/#installation) 14 | - [Vanilla React Native](https://software-mansion.github.io/react-native-gesture-handler/docs/getting-started.html#installation) 15 | 16 | Now, install react-native-quick scroll 17 | 18 | Install with npm 19 | 20 | ```bash 21 | npm install --save react-native-quick-scroll 22 | ``` 23 | Or install with Yarn 24 | 25 | ```bash 26 | yarn add react-native-quick-scroll 27 | ``` 28 | 29 | ## Usage 30 | 31 | ```js 32 | import QuickScrollList from 'react-native-quick-scroll'; 33 | ``` 34 | 35 | ```js 36 | String(item.id)} 38 | data={dataArray} 39 | renderItem={this.renderItem} 40 | itemHeight={60} 41 | viewportHeight={ScreenHeight - TabHeight} 42 | /> 43 | ``` 44 | 45 | ## Props 46 | 47 | Inherits all valid [FlatList props](https://facebook.github.io/react-native/docs/flatlist#props), except `ref` and `showsVerticalScrollIndicator`. Also accepts the following props: 48 | 49 | | Prop | Type | Default Value | Description | Required | 50 | |-----|-----|-----|-----|-----| 51 | | `itemHeight` | `number` | | Height of an item in the FlatList | Yes | 52 | | `viewportHeight` | `number` | | Height of the FlatList area visible on screen at a given time | Yes | 53 | | `thumbHeight` | `number` | `60` | Height of the scroll bar thumb | No | 54 | | `touchAreaWidth` | `number` | `25` | Width of the touchable area that extends from the left edge of the thumb | No | 55 | | `flashDuration` | `number` | `40` | The time taken by the animation to move scroll bar on-screen after the scroll has begun (in ms) | No | 56 | | `flashOutDuration` | `number` | `2000` | The time after which scroll bar disappears (in ms) | No | 57 | | `rightOffset` | `number` | `10` | The distance of the scroll bar from the right edge of screen | No | 58 | | `thumbStyle` | `object` | | Style object for the scroll bar thumb (Don't pass `height` here, use the `thumbHeight` prop instead) | No | 59 | | `scrollbarStyle` | `object` | | Style object for the scroll bar | No | 60 | | `containerStyle` | `object` | | Style object for the parent container | No | 61 | | `hiddenPosition` | `number` | `ScreenWidth + 10` | The off-screen position where the scroll bar thumb moves to after `flashOutDuration` | No | 62 | 63 | 64 | ## Todo (PRs welcome!) 65 | - [x] Native driver support 66 | - [x] Reimplement with Gesture Handler 67 | - [ ] Replace Animated with Reanimated 68 | - [ ] Add TypeScript typings 69 | - [ ] Support for horizontal FlatList 70 | - [x] Support for FlatList ref -------------------------------------------------------------------------------- /demo/rec.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/farshed/react-native-quick-scroll/c78449e7c4b69baf3f81452bef54b7d781326832/demo/rec.gif -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | export { default } from './lib/QuickScrollList'; 2 | -------------------------------------------------------------------------------- /lib/QuickScrollList.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { View, FlatList, Animated, Dimensions } from 'react-native'; 3 | import { PanGestureHandler } from 'react-native-gesture-handler'; 4 | 5 | const ScreenWidth = Dimensions.get('window').width; 6 | 7 | class QuickScrollList extends React.Component { 8 | static defaultProps = { 9 | flashDuration: 40, 10 | flashOutDuration: 2000, 11 | rightOffset: 10, 12 | thumbHeight: 60, 13 | hiddenPosition: ScreenWidth + 10, 14 | touchAreaWidth: 25, 15 | thumbStyle: {}, 16 | scrollbarStyle: {}, 17 | containerStyle: {} 18 | }; 19 | 20 | position = new Animated.Value(0); 21 | scrollBar = new Animated.Value(ScreenWidth); 22 | flatlistRef = React.createRef(); 23 | disableOnScrollEvent = false; 24 | 25 | createRef = (ref) => { 26 | this.flatlistRef = ref; 27 | this.props.ref && this.props.ref(ref); 28 | }; 29 | 30 | onThumbDrag = (event) => { 31 | const { data, itemHeight, thumbHeight, viewportHeight } = this.props; 32 | const availableHeight = viewportHeight - thumbHeight; 33 | const positionY = this.position.__getValue(); 34 | const gestureY = event.nativeEvent.absoluteY; 35 | if (gestureY >= 0 && gestureY <= availableHeight) { 36 | this.disableOnScrollEvent = true; 37 | const thumbPos = (positionY / (viewportHeight - thumbHeight)).toFixed(3); 38 | let lastIndex = data.length - Math.floor(viewportHeight / itemHeight) + 1; 39 | let index = Math.floor(lastIndex * thumbPos); 40 | if (index > lastIndex) index = lastIndex; 41 | if (index < 0) index = 0; 42 | Animated.event([{ nativeEvent: { absoluteY: this.position } }])(event); 43 | this.flatlistRef.scrollToIndex({ 44 | index, 45 | viewPosition: 0, 46 | animated: true 47 | }); 48 | } 49 | }; 50 | 51 | moveThumbOnScroll = (e) => { 52 | if (this.disableOnScrollEvent) { 53 | this.disableOnScrollEvent = false; 54 | return; 55 | } 56 | const { itemHeight, data, thumbHeight, viewportHeight } = this.props; 57 | const listHeight = data.length * itemHeight; 58 | const endPosition = listHeight - viewportHeight; 59 | const offsetY = e.nativeEvent.contentOffset.y; 60 | const diff = (viewportHeight - thumbHeight) / endPosition; 61 | this.position.setValue(offsetY * diff); 62 | }; 63 | 64 | flashScrollBar = () => { 65 | const { flashDuration, rightOffset } = this.props; 66 | Animated.timing(this.scrollBar, { 67 | toValue: ScreenWidth - rightOffset, 68 | duration: flashDuration, 69 | useNativeDriver: true 70 | }).start(); 71 | }; 72 | 73 | onScroll = (event, gesture) => { 74 | this.flashScrollBar(); 75 | this.moveThumbOnScroll(event); 76 | this.props.onScroll && this.props.onScroll(event, gesture); 77 | }; 78 | 79 | onScrollGlideEnd = (event, gesture) => { 80 | const { flashDuration, flashOutDuration } = this.props; 81 | const flashOut = Animated.timing(this.scrollBar, { 82 | toValue: this.props.hiddenPosition, 83 | duration: flashDuration, 84 | useNativeDriver: true 85 | }); 86 | setTimeout(() => flashOut.start(), flashOutDuration); 87 | this.props.onMomentumScrollEnd && this.props.onMomentumScrollEnd(event, gesture); 88 | }; 89 | 90 | convertStyle(prop) { 91 | if (Array.isArray(prop)) { 92 | let propObj = {}; 93 | prop.forEach((val) => { 94 | propObj = { ...propObj, ...val }; 95 | }); 96 | return propObj; 97 | } 98 | return prop; 99 | } 100 | 101 | render() { 102 | //prettier-ignore 103 | const { thumbHeight, thumbStyle, scrollbarStyle, containerStyle, viewportHeight, touchAreaWidth } = this.props; 104 | const rightOffset = { transform: [{ translateX: this.scrollBar }] }; 105 | const thumbTransform = { transform: [{ translateY: this.position }] }; 106 | return ( 107 | 108 | {}} 116 | /> 117 | 124 | 128 | 136 | 137 | 138 | 139 | ); 140 | } 141 | } 142 | 143 | export default QuickScrollList; 144 | 145 | const styles = { 146 | mainWrapper: { 147 | flex: 1 148 | }, 149 | scrollBar: { 150 | position: 'absolute', 151 | width: 10, 152 | backgroundColor: 'transparent', 153 | alignItems: 'center' 154 | }, 155 | thumb: { 156 | width: 4, 157 | borderRadius: 4, 158 | backgroundColor: '#4C4C4C', 159 | elevation: 2 160 | } 161 | }; 162 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-quick-scroll", 3 | "version": "2.1.0", 4 | "main": "index.js", 5 | "author": "Faisal Arshed", 6 | "license": "MIT", 7 | "private": false, 8 | "repository": { 9 | "type": "git", 10 | "url": "https://github.com/farshed/react-native-quick-scroll.git" 11 | }, 12 | "keywords": [ 13 | "react-native", 14 | "fast-scroll", 15 | "flatlist", 16 | "custom", 17 | "scrollbar", 18 | "quick-scroll" 19 | ] 20 | } 21 | --------------------------------------------------------------------------------