Skip to content

Instantly share code, notes, and snippets.

@oxyii
Created August 12, 2021 11:29
Show Gist options
  • Save oxyii/3502ee07e59ffec22dd0a1d88e214922 to your computer and use it in GitHub Desktop.
Save oxyii/3502ee07e59ffec22dd0a1d88e214922 to your computer and use it in GitHub Desktop.
react-native-web-swiper custom margin
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