Created
August 12, 2021 11:29
-
-
Save oxyii/3502ee07e59ffec22dd0a1d88e214922 to your computer and use it in GitHub Desktop.
react-native-web-swiper custom margin
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import React from 'react'; | |
| import { Animated, Dimensions, PanResponder, StyleSheet, Text, TouchableOpacity, View } from 'react-native'; | |
| const MARGIN = 18; | |
| const styles = StyleSheet.create({ | |
| container: { | |
| flex: 1, | |
| marginHorizontal: MARGIN, | |
| backgroundColor: 'transparent', | |
| }, | |
| sliderContainer: { | |
| backgroundColor: 'transparent', | |
| marginHorizontal: MARGIN * -1, | |
| overflow: 'hidden', | |
| position: 'relative', | |
| flex: 1, | |
| }, | |
| controlsWrapperStyle: { | |
| position: 'absolute', | |
| alignItems: 'center', | |
| justifyContent: 'space-between', | |
| right: 0, | |
| bottom: 0, | |
| padding: 10, | |
| }, | |
| dotsWrapperStyle: { | |
| alignItems: 'center', | |
| justifyContent: 'center', | |
| }, | |
| activeDotStyle: { | |
| backgroundColor: '#007aff', | |
| width: 8, | |
| height: 8, | |
| borderRadius: 4, | |
| marginLeft: 3, | |
| marginRight: 3, | |
| marginTop: 3, | |
| marginBottom: 3, | |
| }, | |
| dotStyle: { | |
| backgroundColor: 'rgba(0,0,0,.2)', | |
| width: 8, | |
| height: 8, | |
| borderRadius: 4, | |
| marginLeft: 3, | |
| marginRight: 3, | |
| marginTop: 3, | |
| marginBottom: 3, | |
| }, | |
| prevButtonStyle: { | |
| color: '#777777', | |
| }, | |
| nextButtonStyle: { | |
| color: '#007aff', | |
| }, | |
| }); | |
| export default class Swiper extends React.Component { | |
| constructor(props) { | |
| super(props); | |
| const { width, height } = Dimensions.get('window'); | |
| this.state = { | |
| width, | |
| height, | |
| activeIndex: props.index, | |
| pan: new Animated.ValueXY(), | |
| }; | |
| this._animatedValueX = 0; | |
| this._animatedValueY = 0; | |
| this._panResponder = PanResponder.create({ | |
| onPanResponderTerminationRequest: () => false, | |
| onMoveShouldSetResponderCapture: () => true, | |
| onMoveShouldSetPanResponderCapture: (e, gestureState) => { | |
| if (!this.props.swipingEnabled) { | |
| return false; | |
| } | |
| if (this.props.onAnimationStart) { | |
| this.props.onAnimationStart(); | |
| } | |
| const allow = Math.abs(this.props.direction === 'row' ? gestureState.dx : gestureState.dy) > 5; | |
| if (allow) { | |
| this.stopAutoplay(); | |
| } | |
| return allow; | |
| }, | |
| onPanResponderGrant: (e, gestureState) => this._fixState(), | |
| onPanResponderMove: Animated.event( | |
| [null, this.props.direction === 'row' ? { dx: this.state.pan.x } : { dy: this.state.pan.y }], | |
| { useNativeDriver: false }, | |
| ), | |
| onPanResponderRelease: (e, gesture) => { | |
| const correction = this.props.direction === 'row' ? gesture.moveX - gesture.x0 : gesture.moveY - gesture.y0; | |
| this.startAutoplay(); | |
| if ( | |
| Math.abs(correction) < | |
| (this.props.direction === 'row' ? this.state.width : this.state.height) * this.props.actionMinWidth | |
| ) { | |
| return Animated.spring(this.state.pan, { toValue: { x: 0, y: 0 }, useNativeDriver: false }).start(() => { | |
| if (this.props.onAnimationEnd) { | |
| this.props.onAnimationEnd(this.state.activeIndex); | |
| } | |
| }); | |
| } | |
| this._changeIndex(correction > 0 ? -1 : 1); | |
| }, | |
| }); | |
| } | |
| componentDidMount() { | |
| this.state.pan.x.addListener(value => (this._animatedValueX = value.value)); | |
| this.state.pan.y.addListener(value => (this._animatedValueY = value.value)); | |
| this.startAutoplay(); | |
| } | |
| componentWillUnmount() { | |
| this.stopAutoplay(); | |
| this.state.pan.x.removeAllListeners(); | |
| this.state.pan.y.removeAllListeners(); | |
| } | |
| startAutoplay() { | |
| this.stopAutoplay(); | |
| if (this.props.autoplayTimeout) { | |
| this.autoplay = setTimeout(() => { | |
| this.moveUpDown(this.props.autoplayTimeout < 0); | |
| }, Math.abs(this.props.autoplayTimeout) * 1000); | |
| } | |
| } | |
| stopAutoplay() { | |
| this.autoplay && clearTimeout(this.autoplay); | |
| } | |
| goto(index) { | |
| if (index - this.state.activeIndex === 0) { | |
| return; | |
| } | |
| this._fixState(); | |
| if (this.props.onAnimationStart) { | |
| this.props.onAnimationStart(); | |
| } | |
| this._changeIndex(index - this.state.activeIndex); | |
| } | |
| moveUpDown(down = false) { | |
| this._fixState(); | |
| if (this.props.onAnimationStart) { | |
| this.props.onAnimationStart(); | |
| } | |
| this._changeIndex(down ? -1 : 1); | |
| } | |
| _fixState() { | |
| this._animatedValueX = this.props.direction === 'row' ? this.state.width * this.state.activeIndex * -1 + MARGIN : 0; | |
| this._animatedValueY = this.props.direction === 'row' ? 0 : this.state.height * this.state.activeIndex * -1; | |
| this.state.pan.setOffset({ x: this._animatedValueX, y: this._animatedValueY }); | |
| this.state.pan.setValue({ x: 0, y: 0 }); | |
| } | |
| _changeIndex(delta = 1) { | |
| let move = { x: 0, y: 0 }; | |
| let skipChanges = !delta; | |
| let calcDelta = delta; | |
| if (this.state.activeIndex <= 0 && delta < 0) { | |
| skipChanges = !this.props.loop; | |
| calcDelta = this.count + delta; | |
| } else if (this.state.activeIndex + 1 >= this.count && delta > 0) { | |
| skipChanges = !this.props.loop; | |
| calcDelta = -1 * this.state.activeIndex + delta - 1; | |
| } | |
| if (skipChanges) { | |
| return Animated.spring(this.state.pan, { toValue: move, useNativeDriver: false }).start(() => { | |
| if (this.props.onAnimationEnd) { | |
| this.props.onAnimationEnd(this.state.activeIndex); | |
| } | |
| }); | |
| } | |
| this.stopAutoplay(); | |
| let index = this.state.activeIndex + calcDelta; | |
| this.setState({ activeIndex: index }); | |
| if (this.props.direction === 'row') { | |
| move.x = this.state.width * -1 * calcDelta; | |
| } else { | |
| move.y = this.state.height * -1 * calcDelta; | |
| } | |
| Animated.spring(this.state.pan, { toValue: move, useNativeDriver: false }).start(() => { | |
| if (this.props.onAnimationEnd) { | |
| this.props.onAnimationEnd(index); | |
| } | |
| }); | |
| this.startAutoplay(); | |
| this.props.onIndexChanged && this.props.onIndexChanged(index); | |
| } | |
| _onLayout({ | |
| nativeEvent: { | |
| layout: { width, height }, | |
| }, | |
| }) { | |
| if (this.state.width !== width || this.state.height !== height) { | |
| this.setState({ width, height }, () => this._fixState()); | |
| } | |
| } | |
| render() { | |
| const { pan, width, height, activeIndex } = this.state; | |
| const { | |
| direction, | |
| containerStyle, | |
| swipeAreaStyle, | |
| swipeWrapperStyle, | |
| controlsWrapperStyle, | |
| dotsWrapperStyle, | |
| dotElement, | |
| dotStyle, | |
| activeDotElement, | |
| activeDotStyle, | |
| prevButtonElement, | |
| prevButtonStyle, | |
| prevButtonText, | |
| nextButtonElement, | |
| nextButtonStyle, | |
| nextButtonText, | |
| loop, | |
| buttonsEnabled, | |
| } = this.props; | |
| const overRangeButtonsOpacity = !loop | |
| ? this.props.overRangeButtonsOpacity | |
| : this.props.overRangeButtonsOpacity || 1; | |
| let { children } = this.props; | |
| if (!Array.isArray(children)) { | |
| children = [children]; | |
| } | |
| this.count = children.length; | |
| return ( | |
| <View style={[styles.container, containerStyle]} onLayout={this._onLayout.bind(this)}> | |
| <View style={[styles.sliderContainer, swipeAreaStyle]}> | |
| <Animated.View | |
| style={[ | |
| { | |
| position: 'relative', | |
| top: 0, | |
| left: 0, | |
| }, | |
| swipeWrapperStyle, | |
| { | |
| flexDirection: direction, | |
| width: direction === 'row' ? width * this.count : width, | |
| height: direction === 'row' ? height : height * this.count, | |
| }, | |
| { transform: [{ translateX: pan.x }, { translateY: pan.y }] }, | |
| ]} | |
| {...this._panResponder.panHandlers} | |
| > | |
| {children.map((el, i) => ( | |
| <View key={i} style={{ width, height, paddingHorizontal: 6 }}> | |
| {el} | |
| </View> | |
| ))} | |
| </Animated.View> | |
| {!buttonsEnabled ? null : ( | |
| <View | |
| style={[ | |
| styles.controlsWrapperStyle, | |
| { | |
| flexDirection: direction, | |
| }, | |
| direction === 'row' ? { left: 0 } : { top: 0 }, | |
| controlsWrapperStyle, | |
| ]} | |
| > | |
| <View style={{ opacity: !activeIndex ? overRangeButtonsOpacity : 1 }}> | |
| <TouchableOpacity disabled={!activeIndex && !loop} onPress={() => this.moveUpDown(true)}> | |
| {prevButtonElement || <Text style={[styles.prevButtonStyle, prevButtonStyle]}>{prevButtonText}</Text>} | |
| </TouchableOpacity> | |
| </View> | |
| <View style={[{ flexDirection: direction }, styles.dotsWrapperStyle, dotsWrapperStyle]}> | |
| {children.map((el, i) => ( | |
| <View key={i}> | |
| {i === activeIndex | |
| ? activeDotElement || <View style={[styles.activeDotStyle, activeDotStyle]} /> | |
| : dotElement || <View style={[styles.dotStyle, dotStyle]} />} | |
| </View> | |
| ))} | |
| </View> | |
| <View style={{ opacity: activeIndex + 1 >= this.count ? overRangeButtonsOpacity : 1 }}> | |
| <TouchableOpacity disabled={activeIndex + 1 >= this.count && !loop} onPress={() => this.moveUpDown()}> | |
| {nextButtonElement || <Text style={[styles.nextButtonStyle, nextButtonStyle]}>{nextButtonText}</Text>} | |
| </TouchableOpacity> | |
| </View> | |
| </View> | |
| )} | |
| </View> | |
| </View> | |
| ); | |
| } | |
| } | |
| Swiper.defaultProps = { | |
| direction: 'row', | |
| index: 0, | |
| actionMinWidth: 0.25, | |
| overRangeButtonsOpacity: 0, | |
| loop: false, | |
| autoplayTimeout: 0, | |
| swipingEnabled: true, | |
| buttonsEnabled: true, | |
| prevButtonText: 'prev', | |
| nextButtonText: 'next', | |
| }; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment