rn-content-loader
Version:
A react native content loader without expo
176 lines (164 loc) • 4.43 kB
JavaScript
import React, { Component } from "react";
import { View, StyleSheet, Animated, Text, Dimensions } from "react-native";
import Svg, {
Rect,
Defs,
LinearGradient,
Stop,
ClipPath,
G
} from "react-native-svg";
import PropTypes from "prop-types";
const AnimatedSvg = Animated.createAnimatedComponent(Svg);
const { interpolate } = require("d3-interpolate");
const { width } = Dimensions.get("window");
class ContentLoader extends Component {
constructor(props) {
super(props);
this.state = {
offsetValues: ["-2", "-1.5", "-1"],
offsets: [
"0.0001",
"0.0002",
"0.0003" // Avoid duplicate value cause error in Android
],
frequence: props.duration / 2
};
this._isMounted = false;
this._animate = new Animated.Value(0);
this.loopAnimation = this.loopAnimation.bind(this);
}
offsetValueBound(x) {
if (x > 1) {
return "1";
}
if (x < 0) {
return "0";
}
return x;
}
componentDidMount(props) {
this._isMounted = true;
this.loopAnimation();
}
componentWillUnmount() {
this._isMounted = false;
}
loopAnimation() {
if (!this._isMounted) return;
// setup interpolate
let interpolator = interpolate(this.state, {
offsetValues: ["1", "1.5", "2"]
});
// start animation
let start = Date.now();
this._animation = () => {
const now = Date.now();
let t = (now - start) / this.props.duration;
if (t > 1) {
t = 1;
}
let newState = interpolator(t);
let offsetValues = [];
offsetValues[0] = this.offsetValueBound(newState.offsetValues[0]);
offsetValues[1] = this.offsetValueBound(newState.offsetValues[1]);
offsetValues[2] = this.offsetValueBound(newState.offsetValues[2]);
// Make sure at least two offsets is different
if (
offsetValues[0] !== offsetValues[1] ||
offsetValues[0] !== offsetValues[2] ||
offsetValues[1] !== offsetValues[2]
) {
this._isMounted && this.setState({ offsets: offsetValues });
}
if (t < 1) {
requestAnimationFrame(this._animation);
}
};
requestAnimationFrame(this._animation);
// Setup loop animation
Animated.sequence([
Animated.timing(this._animate, {
toValue: 1,
duration: this.state.frequence,
useNativeDriver: false
}),
Animated.timing(this._animate, {
toValue: 0,
duration: this.state.frequence,
useNativeDriver: false
})
]).start(event => {
if (event.finished) {
this.loopAnimation();
}
});
}
render() {
let { height, width, viewBox=`0 0 ${width} ${height}` } = this.props;
return (
<AnimatedSvg width={width} height={height} viewBox={viewBox}>
<Defs>
<LinearGradient
id="grad"
x1={this.props.x1}
y1={this.props.y1}
x2={this.props.x2}
y2={this.props.y2}
>
<Stop
offset={this.state.offsets[0]}
stopColor={this.props.primaryColor}
stopOpacity="1"
/>
<Stop
offset={this.state.offsets[1]}
stopColor={this.props.secondaryColor}
stopOpacity="1"
/>
<Stop
offset={this.state.offsets[2]}
stopColor={this.props.primaryColor}
stopOpacity="1"
/>
</LinearGradient>
<ClipPath id="clip">
<G>{this.props.children}</G>
</ClipPath>
</Defs>
<Rect
x="0"
y="0"
height={height}
width={width}
fill="url(#grad)"
clipPath="url(#clip)"
/>
</AnimatedSvg>
);
}
}
ContentLoader.propTypes = {
primaryColor: PropTypes.string,
secondaryColor: PropTypes.string,
duration: PropTypes.number,
width: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
height: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
viewBox: PropTypes.string,
x1: PropTypes.string,
y1: PropTypes.string,
x2: PropTypes.string,
y2: PropTypes.string
};
ContentLoader.defaultProps = {
primaryColor: "#eeeeee",
secondaryColor: "#dddddd",
duration: 2000,
width: 300,
height: 200,
x1: "0",
y1: "0",
x2: "100%",
y2: "0"
};
export default ContentLoader;