├── .gitignore ├── Example ├── .DS_Store ├── App.js └── index.js ├── FlatList.js ├── ParallaxImage.js ├── README.md ├── demo-min.gif ├── index.js ├── package-lock.json └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /Example/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davrosull/react-native-parallax-flatlist/a12e55973f24dc6f4112149ee3475223f4d5cb01/Example/.DS_Store -------------------------------------------------------------------------------- /Example/App.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @flow 3 | */ 4 | 5 | import React, { Component } from 'react'; 6 | import { 7 | Platform, 8 | StyleSheet, 9 | Text, 10 | View, 11 | Image, 12 | Dimensions, 13 | PixelRatio, 14 | } from 'react-native'; 15 | import FlatList, { ParallaxImage } from 'react-native-parallax-flatlist'; 16 | 17 | const IMAGE_WIDTH = Dimensions.get('window').width; 18 | const IMAGE_HEIGHT = 200; 19 | const PIXEL_RATIO = 2; 20 | const PARALLAX_FACTOR = 0.2; 21 | const IMAGE_URI_PREFIX = 22 | 'https://loremflickr.com/' + 23 | IMAGE_WIDTH * PIXEL_RATIO + 24 | '/' + 25 | Math.round(IMAGE_HEIGHT * (1 + PARALLAX_FACTOR * PIXEL_RATIO) * PIXEL_RATIO) + 26 | '/'; 27 | 28 | const data = [ 29 | { 30 | title: '(=^ ◡ ^=)', 31 | keyword: 'cat', 32 | image: 'cat', 33 | }, 34 | { 35 | title: 'o(U・ω・)⊃', 36 | keyword: 'dog', 37 | image: 'dog', 38 | }, 39 | { 40 | title: '⊂((・⊥・))⊃', 41 | keyword: 'monkey', 42 | image: 'cat', 43 | }, 44 | { 45 | title: '(・⊝・)', 46 | keyword: 'penguin', 47 | image: 'dog', 48 | }, 49 | { 50 | title: '§・ω・§', 51 | keyword: 'sheep', 52 | image: 'cat', 53 | }, 54 | { 55 | title: '/|\\( ;,;)/|\\', 56 | keyword: 'bat', 57 | image: 'dog', 58 | }, 59 | { 60 | title: "-o,,o,,o'", 61 | keyword: 'ant', 62 | image: 'cat', 63 | }, 64 | { 65 | title: '(*)>\n/ ) \n/" ', 66 | keyword: 'bird', 67 | image: 'dog', 68 | }, 69 | { 70 | title: '( )\n:(III)-\n( ) ', 71 | keyword: 'bee', 72 | image: 'cat', 73 | }, 74 | { 75 | title: 'O_______O\n( ^ ~ ^ )\n(,,)()(,,)\n( ) ( )', 76 | keyword: 'bear', 77 | image: 'dog', 78 | }, 79 | ]; 80 | 81 | export default class App extends Component<{}> { 82 | render() { 83 | return ( 84 | index} 87 | renderItem={({ item }) => ( 88 | 89 | 95 | {item.title} 96 | 97 | Source: {IMAGE_URI_PREFIX + item.keyword} 98 | 99 | 100 | )} 101 | /> 102 | ); 103 | } 104 | } 105 | 106 | const styles = StyleSheet.create({ 107 | overlay: { 108 | alignItems: 'center', 109 | justifyContent: 'center', 110 | backgroundColor: 'rgba(0,0,0,0.3)', 111 | height: IMAGE_HEIGHT, 112 | }, 113 | title: { 114 | fontSize: 20, 115 | textAlign: 'center', 116 | lineHeight: 25, 117 | fontWeight: 'bold', 118 | color: 'white', 119 | shadowOffset: { 120 | width: 0, 121 | height: 0, 122 | }, 123 | shadowRadius: 1, 124 | shadowColor: 'black', 125 | shadowOpacity: 0.8, 126 | }, 127 | url: { 128 | opacity: 0.5, 129 | fontSize: 10, 130 | position: 'absolute', 131 | color: 'white', 132 | left: 5, 133 | bottom: 5, 134 | }, 135 | }); 136 | -------------------------------------------------------------------------------- /Example/index.js: -------------------------------------------------------------------------------- 1 | import { AppRegistry } from 'react-native'; 2 | import App from './App'; 3 | 4 | AppRegistry.registerComponent('examp2', () => App); 5 | -------------------------------------------------------------------------------- /FlatList.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { FlatList as List, Dimensions, Animated, Text } from 'react-native'; 4 | import _ from 'lodash'; 5 | 6 | // Native driver for scroll events 7 | // See: https://facebook.github.io/react-native/blog/2017/02/14/using-native-driver-for-animated.html 8 | const AnimatedFlatList = Animated.createAnimatedComponent(List); 9 | 10 | /* Component ==================================================================== */ 11 | class FlatList extends PureComponent { 12 | static propTypes = { 13 | ...List.propTypes, 14 | }; 15 | 16 | constructor(props) { 17 | super(props); 18 | 19 | this._flatlist = undefined; 20 | this._scrollPos = new Animated.Value(0); 21 | 22 | this.state = { 23 | _flatlist: undefined, 24 | }; 25 | // Native driver for scroll events 26 | const scrollEventConfig = { 27 | listener: this._onScroll, 28 | useNativeDriver: true, 29 | }; 30 | 31 | this._onScrollHandler = Animated.event( 32 | [{ nativeEvent: { contentOffset: { y: this._scrollPos } } }], 33 | scrollEventConfig 34 | ); 35 | } 36 | 37 | setNativeProps(nativeProps) { 38 | this._flatlist.setNativeProps(nativeProps); 39 | } 40 | 41 | applyPropsToParallaxImages = function(children, props) { 42 | if (Array.isArray(children)) { 43 | return React.Children.map(children, child => { 44 | if (Array.isArray(child)) { 45 | return applyPropsToParallaxImages(child, props); 46 | } 47 | if (child.type.displayName === 'ParallaxImage') { 48 | return React.cloneElement(child, props); 49 | } 50 | return child; 51 | }); 52 | } 53 | 54 | if (children.type.displayName === 'ParallaxImage') { 55 | return React.cloneElement(children, props); 56 | } 57 | return children; 58 | }; 59 | 60 | render() { 61 | var { ref, children, onScroll, renderItem, ...props } = this.props; 62 | var { _flatlist } = this.state; 63 | var handleScroll = onScroll 64 | ? event => { 65 | this._onScrollHandler(event); 66 | onScroll(event); 67 | } 68 | : this._onScrollHandler; 69 | 70 | return ( 71 | { 73 | this._flatlist = list; 74 | this.setState({ _flatlist: list }); 75 | }} 76 | scrollEventThrottle={16} 77 | onScroll={handleScroll} 78 | renderItem={item => { 79 | const parallaxProps = { 80 | scrollPosition: this._scrollPos, 81 | flatlistRef: this._flatlist, 82 | }; 83 | const renderedItem = React.cloneElement( 84 | renderItem(item), 85 | parallaxProps 86 | ); 87 | 88 | const parallaxedItem = React.cloneElement( 89 | renderedItem, 90 | parallaxProps, 91 | this.applyPropsToParallaxImages( 92 | renderedItem.props.children, 93 | parallaxProps 94 | ) 95 | ); 96 | 97 | return parallaxedItem; 98 | }} 99 | {...props} 100 | /> 101 | ); 102 | } 103 | } 104 | 105 | /* Export Component ==================================================================== */ 106 | export default FlatList; 107 | -------------------------------------------------------------------------------- /ParallaxImage.js: -------------------------------------------------------------------------------- 1 | // Parallax effect inspired by https://github.com/oblador/react-native-parallax/ 2 | 3 | import React, { Component } from 'react'; 4 | import { 5 | View, 6 | ViewPropTypes, 7 | Image, 8 | Animated, 9 | Easing, 10 | ActivityIndicator, 11 | findNodeHandle, 12 | StyleSheet, 13 | Dimensions, 14 | } from 'react-native'; 15 | import PropTypes from 'prop-types'; 16 | import _ from 'lodash'; 17 | 18 | const WINDOW_HEIGHT = Dimensions.get('window').height; 19 | 20 | const styles = StyleSheet.create({ 21 | container: { 22 | alignItems: 'center', 23 | justifyContent: 'center', 24 | overflow: 'hidden', 25 | }, 26 | }); 27 | 28 | export default class ParallaxImage extends Component { 29 | static propTypes = { 30 | ...Image.propTypes, 31 | flatlistRef: PropTypes.object, 32 | scrollPosition: PropTypes.object, 33 | }; 34 | 35 | static defaultProps = { 36 | parallaxFactor: 0.2, 37 | }; 38 | 39 | constructor(props) { 40 | super(props); 41 | this.isLayoutStale = true; 42 | this.state = { 43 | offset: 0, 44 | width: 0, 45 | height: 0, 46 | }; 47 | } 48 | 49 | setNativeProps(nativeProps) { 50 | this._container.setNativeProps(nativeProps); 51 | } 52 | 53 | componentWillReceiveProps(nextProps) { 54 | if (nextProps !== this.props) { 55 | this.isLayoutStale = true; 56 | } 57 | } 58 | 59 | handleLayout = event => { 60 | if (this.isLayoutStale) { 61 | this._container.measure(this.handleMeasure); 62 | } 63 | }; 64 | 65 | handleMeasure = (ox, oy, width, height, px, py) => { 66 | this.isLayoutStale = false; 67 | const { flatlistRef } = this.props; 68 | 69 | this.setState({ 70 | offset: py, 71 | height, 72 | width, 73 | }); 74 | }; 75 | 76 | get image() { 77 | const { offset, width, height } = this.state; 78 | const { scrollPosition, parallaxFactor, style, ...other } = this.props; 79 | 80 | var parallaxPadding = height * parallaxFactor; 81 | 82 | var parallaxStyle = { 83 | width: width, 84 | height: height + parallaxPadding * 2, 85 | }; 86 | if (scrollPosition) { 87 | parallaxStyle.transform = [ 88 | { 89 | translateY: scrollPosition.interpolate({ 90 | inputRange: [offset - height * 2, offset + height * 2], 91 | outputRange: [-parallaxPadding, parallaxPadding], 92 | extrapolate: 'clamp', 93 | }), 94 | }, 95 | ]; 96 | } else { 97 | parallaxStyle.transform = [{ translateY: -parallaxPadding }]; 98 | } 99 | 100 | return ( 101 | 102 | 107 | 108 | ); 109 | } 110 | 111 | render() { 112 | return ( 113 | { 115 | this._container = c; 116 | }} 117 | type={'ParallaxImage'} 118 | pointerEvents={'none'} 119 | style={[this.props.style, { overflow: 'hidden' }]} 120 | onLayout={this.handleLayout} 121 | > 122 | {this.image} 123 | 124 | ); 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-native-parallax-flatlist 2 | 3 | A drop in replacement for `FlatList` that allows for a Parallax effect on images (by using the ParallaxImage component included). 4 | 5 | ## Installation 6 | 7 | ``` 8 | npm install --save react-native-parallax-flatlist 9 | ``` 10 | 11 | ## Usage 12 | 13 | ```js 14 | import FlatList, { ParallaxImage } from './react-native-parallax-flatlist'; 15 | 16 | export default class ParallaxView extends Component { 17 | 18 | const data = [ 19 | { 20 | title: '(=^ ◡ ^=)', 21 | image: 'http://loremflickr.com/640/480/cat', 22 | }, 23 | { 24 | title: 'o(U・ω・)⊃', 25 | image: 'http://loremflickr.com/640/480/dog', 26 | }]; 27 | 28 | render() { 29 | return ( 30 | index} 33 | renderItem={({ item }) => ( 34 | 35 | 40 | {item.title} 47 | 48 | )} 49 | /> 50 | ); 51 | } 52 | } 53 | ``` 54 | 55 | ## `FlatList` Properties 56 | 57 | Any [`FlatList` property](http://facebook.github.io/react-native/docs/flatlist.html). 58 | 59 | ## `ParallaxImage` Properties 60 | 61 | Any [`Image` property](http://facebook.github.io/react-native/docs/image.html) and the following: 62 | 63 | | Prop | Description | Default | 64 | | -------------------- | ------------------------------------------------------------------------------------------------ | ------- | 65 | | **`parallaxFactor`** | The speed of the parallax effect. Larger values require taller images or they will be zoomed in. | `0.2` | 66 | 67 | ## Demo 68 | 69 | ![Demo](https://github.com/davrosull/react-native-parallax-flatlist/blob/master/demo-min.gif?raw=true) 70 | 71 | ## Example 72 | 73 | Check full example in the `Example` folder. 74 | 75 | ## Credits 76 | 77 | Inspiration taken from [react-native-parallax](https://github.com/oblador/react-native-parallax) and [react-native-snap-carousel](https://github.com/archriss/react-native-snap-carousel) 78 | 79 | ## License 80 | 81 | [MIT License](http://opensource.org/licenses/mit-license.html). © David Sullivan 82 | -------------------------------------------------------------------------------- /demo-min.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davrosull/react-native-parallax-flatlist/a12e55973f24dc6f4112149ee3475223f4d5cb01/demo-min.gif -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import FlatList from './FlatList'; 2 | import ParallaxImage from './ParallaxImage'; 3 | 4 | export { FlatList as default, ParallaxImage }; 5 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-parallax-flatlist", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "prettier": { 8 | "version": "1.9.2", 9 | "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.9.2.tgz", 10 | "integrity": "sha512-piXx9N2WT8hWb7PBbX1glAuJVIkEyUV9F5fMXFINpZ0x3otVOFKKeGmeuiclFJlP/UrgTckyV606VjH2rNK4bw==", 11 | "dev": true 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-parallax-flatlist", 3 | "version": "1.0.2", 4 | "description": "Parallax effects for images in a React Native FlatList", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "flow check" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/davrosull/react-native-parallax-flatlist.git" 12 | }, 13 | "keywords": ["react", "native", "flatlist", "parallax", "image", "animated"], 14 | "author": "David Sullivan", 15 | "license": "MIT", 16 | "bugs": { 17 | "url": "https://github.com/soulglo/react-native-parallax-flatlist/issues" 18 | }, 19 | "homepage": 20 | "https://github.com/soulglo/react-native-parallax-flatlist#readme", 21 | "peerDependencies": { 22 | "react": ">=15.0.0", 23 | "react-native": "*" 24 | } 25 | } 26 | --------------------------------------------------------------------------------