UNPKG

react-native-snap-carousel

Version:

Swiper component for React Native with previews, multiple layouts, parallax images, performant handling of huge numbers of items, and RTL support. Compatible with Android & iOS.

217 lines (189 loc) 6.79 kB
// Parallax effect inspired by https://github.com/oblador/react-native-parallax/ import React, { Component } from 'react'; import { View, ViewPropTypes, Image, Animated, Easing, ActivityIndicator, findNodeHandle } from 'react-native'; import PropTypes from 'prop-types'; import styles from './ParallaxImage.style'; export default class ParallaxImage extends Component { static propTypes = { ...Image.propTypes, carouselRef: PropTypes.object, // passed from <Carousel /> itemHeight: PropTypes.number, // passed from <Carousel /> itemWidth: PropTypes.number, // passed from <Carousel /> scrollPosition: PropTypes.object, // passed from <Carousel /> sliderHeight: PropTypes.number, // passed from <Carousel /> sliderWidth: PropTypes.number, // passed from <Carousel /> vertical: PropTypes.bool, // passed from <Carousel /> containerStyle: ViewPropTypes ? ViewPropTypes.style : View.propTypes.style, dimensions: PropTypes.shape({ width: PropTypes.number, height: PropTypes.number }), fadeDuration: PropTypes.number, parallaxFactor: PropTypes.number, showSpinner: PropTypes.bool, spinnerColor: PropTypes.string }; static defaultProps = { containerStyle: {}, fadeDuration: 500, parallaxFactor: 0.3, showSpinner: true, spinnerColor: 'rgba(0, 0, 0, 0.4)' } constructor (props) { super(props); this.state = { offset: 0, width: 0, height: 0, status: 1, // 1 -> loading; 2 -> loaded // 3 -> transition finished; 4 -> error animOpacity: new Animated.Value(0) }; this._onLoad = this._onLoad.bind(this); this._onError = this._onError.bind(this); this._measureLayout = this._measureLayout.bind(this); } setNativeProps (nativeProps) { this._container.setNativeProps(nativeProps); } componentDidMount () { this._mounted = true; setTimeout(() => { this._measureLayout(); }, 0); } componentWillUnmount () { this._mounted = false; } _measureLayout () { if (this._container) { const { dimensions, vertical, carouselRef, sliderWidth, sliderHeight, itemWidth, itemHeight } = this.props; if (carouselRef) { this._container.measureLayout( findNodeHandle(carouselRef), (x, y, width, height, pageX, pageY) => { const offset = vertical ? y - ((sliderHeight - itemHeight) / 2) : x - ((sliderWidth - itemWidth) / 2); this.setState({ offset: offset, width: dimensions && dimensions.width ? dimensions.width : Math.ceil(width), height: dimensions && dimensions.height ? dimensions.height : Math.ceil(height) }); } ); } } } _onLoad (event) { const { animOpacity } = this.state; const { fadeDuration, onLoad } = this.props; if (!this._mounted) { return; } this.setState({ status: 2 }); if (onLoad) { onLoad(event); } Animated.timing(animOpacity, { toValue: 1, duration: fadeDuration, easing: Easing.out(Easing.quad), isInteraction: false, useNativeDriver: true }).start(() => { this.setState({ status: 3 }); }); } // If arg is missing from method signature, it just won't be called _onError (event) { const { onError } = this.props; this.setState({ status: 4 }); if (onError) { onError(event); } } get image () { const { status, animOpacity, offset, width, height } = this.state; const { scrollPosition, dimensions, vertical, sliderWidth, sliderHeight, parallaxFactor, style, ...other } = this.props; const parallaxPadding = (vertical ? height : width) * parallaxFactor; const requiredStyles = { position: 'relative' }; const dynamicStyles = { width: vertical ? width : width + parallaxPadding * 2, height: vertical ? height + parallaxPadding * 2 : height, opacity: animOpacity, transform: scrollPosition ? [ { translateX: !vertical ? scrollPosition.interpolate({ inputRange: [offset - sliderWidth, offset + sliderWidth], outputRange: [-parallaxPadding, parallaxPadding], extrapolate: 'clamp' }) : 0 }, { translateY: vertical ? scrollPosition.interpolate({ inputRange: [offset - sliderHeight, offset + sliderHeight], outputRange: [-parallaxPadding, parallaxPadding], extrapolate: 'clamp' }) : 0 } ] : [] }; return ( <Animated.Image {...other} style={[styles.image, style, requiredStyles, dynamicStyles]} onLoad={this._onLoad} onError={status !== 3 ? this._onError : undefined} // prevent infinite-loop bug /> ); } get spinner () { const { status } = this.state; const { showSpinner, spinnerColor } = this.props; return status === 1 && showSpinner ? ( <View style={styles.loaderContainer}> <ActivityIndicator size={'small'} color={spinnerColor} animating={true} /> </View> ) : false; } render () { const { containerStyle } = this.props; return ( <View ref={(c) => { this._container = c; }} pointerEvents={'none'} style={[containerStyle, styles.container]} onLayout={this._measureLayout} > { this.image } { this.spinner } </View> ); } }