react-native-bouncing-ball
Version:
A react-native bouncing ball component for both iOS and Android.
182 lines (156 loc) • 4.89 kB
JavaScript
import React, {PureComponent} from 'react';
import {View, Image, StyleSheet, Dimensions, Animated, Easing} from 'react-native';
import PropTypes from 'prop-types';
class BouncingBalls extends PureComponent {
static propTypes = {
amount: PropTypes.number.isRequired,
animationDuration: PropTypes.number.isRequired,
animationType: PropTypes.func,
minSpeed: PropTypes.number.isRequired,
maxSpeed: PropTypes.number.isRequired,
minSize: PropTypes.number.isRequired,
maxSize: PropTypes.number.isRequired,
imageBall: PropTypes.node,
};
static defaultProps = {
amount: 1,
animationDuration: 5000,
minSpeed: 30,
maxSpeed: 200,
minSize: 40,
maxSize: 100,
animationType: Easing.linear,
};
constructor(props) {
super(props);
this.screenWidth = Dimensions.get('window').width;
this.screenHeight = Dimensions.get('window').height;
this.circles = this.generateCircles();
this.state = {
position: new Animated.ValueXY({x: 0, y: 0}),
};
}
componentDidMount() {
this.traverseCircles();
}
componentWillUnmount() {
this.circles.forEach((item, index) => {
this.state[`position${index}`].stopAnimation();
});
}
traverseCircles() {
this.circles.forEach((circle, index) => {
this.setState({
[`position${index}`]: new Animated.ValueXY({x: circle.props.x, y: circle.props.y}),
}, () => {
const _circle = this.updateCirclePosition(circle.props, index);
this.circleStartAnimation(_circle, index);
});
});
}
circleStartAnimation(circle, index) {
const {animationDuration, animationType} = this.props;
Animated.timing(
this.state[`position${index}`],
{
toValue: {x: circle.x, y: circle.y},
duration: animationDuration,
easing: animationType,
},
).start(() => {
const currentPosition = this.state[`position${index}`];
currentPosition.stopAnimation(() => {
currentPosition.setValue({x: circle.x, y: circle.y});
let _circle = this.updateCirclePosition(circle, index);
requestAnimationFrame(() => this.circleStartAnimation(_circle, index));
});
});
}
updateCirclePosition(circle) {
const _circle = Object.assign({}, circle);
const height = width = circle.style[1].width;
const maxWidth = this.screenWidth - width;
const maxHeight = this.screenHeight - height;
_circle.x = _circle.x + _circle.speedX;
_circle.y = _circle.y + _circle.speedY;
if (_circle.x <= 0) {
_circle.x = 0;
_circle.speedX *= (-1);
} else if (_circle.x >= maxWidth) {
_circle.x = maxWidth;
_circle.speedX *= (-1);
}
if (_circle.y <= 0) {
_circle.y = 0;
_circle.speedY *= (-1);
} else if (_circle.y >= maxHeight) {
_circle.y = maxHeight;
_circle.speedY *= (-1);
}
return _circle;
}
getRangeFromMinToMax(min, max) {
return Math.floor(Math.random() * (max - min) + min);
}
generateCircles() {
const {amount, minSpeed, maxSpeed, minSize, maxSize, imageBall, style, ...restProps} = this.props;
const circles = [];
let width, height, borderRadius, innerStyle, restStyles, item, direction;
if (amount < 1) return null;
for (var i = 0; i < amount; i++) {
height = width = this.getRangeFromMinToMax(minSize, maxSize);
borderRadius = height * 0.5;
direction = Math.round(Math.random()) === 0 ? -1 : 1;
innerStyle = {
height,
width,
borderRadius,
};
restStyles = {
x: this.getRangeFromMinToMax(0, this.screenWidth - width),
y: this.getRangeFromMinToMax(0, this.screenHeight - height),
speedX: direction * this.getRangeFromMinToMax(minSpeed, maxSpeed),
speedY: direction * this.getRangeFromMinToMax(minSpeed, maxSpeed),
};
item = imageBall ?
<Image
source={imageBall}
style={[styles.circle, {...innerStyle}, {...style}]} {...restStyles} {...restProps}
/> : <View
style={[styles.circle, {...innerStyle}, {...style}]} {...restStyles} {...restProps}
/>;
circles.push(item);
}
return circles;
}
render() {
return <View style={styles.container}>
{
this.circles.map(((item, index) => {
return (
<Animated.View key={index}
style={[this.state[`position${index}`] && this.state[`position${index}`].getLayout()]}
>
{item}
</Animated.View>
);
}))
}
</View>;
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
position: 'absolute',
width: '100%',
height: '100%',
},
circle: {
position: 'absolute',
},
circlePosition: {
position: 'absolute',
},
});
export default BouncingBalls;