2016-12-04 11:56:41 +01:00
|
|
|
import React, { PropTypes, Component } from 'react';
|
2016-12-05 12:57:21 +01:00
|
|
|
import styles from './progress-styles.scss';
|
2016-12-04 11:56:41 +01:00
|
|
|
|
|
|
|
class Progress extends Component {
|
|
|
|
constructor (props) {
|
|
|
|
super(props);
|
|
|
|
|
|
|
|
this.state = {
|
|
|
|
percentage: props.initialAnimation ? 0 : props.percentage,
|
2016-12-17 13:53:50 +01:00
|
|
|
percentageText: props.initialAnimation ? 0 : props.percentage,
|
2016-12-04 11:56:41 +01:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
componentDidMount () {
|
|
|
|
if (this.props.initialAnimation) {
|
|
|
|
this.initialTimeout = setTimeout(() => {
|
2016-12-17 13:53:50 +01:00
|
|
|
this.rafTimerInit = window.requestAnimationFrame(() => {
|
2016-12-04 11:56:41 +01:00
|
|
|
this.setState({
|
|
|
|
percentage: this.props.percentage,
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}, 0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
componentWillReceiveProps ({ percentage }) {
|
2016-12-17 13:53:50 +01:00
|
|
|
if (this.state.percentage !== percentage) {
|
|
|
|
const nextState = { percentage };
|
|
|
|
if (this.props.animatePercentageText) {
|
|
|
|
this.animateTo(percentage, this.getTarget(percentage));
|
|
|
|
} else {
|
|
|
|
nextState.percentageText = percentage;
|
|
|
|
}
|
|
|
|
this.setState(nextState);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
getTarget (target) {
|
|
|
|
const start = this.state.percentageText;
|
2016-12-17 19:49:31 +01:00
|
|
|
const TOTAL_ANIMATION_TIME = 5000;
|
2016-12-17 13:53:50 +01:00
|
|
|
const diff = start > target ? -(start - target) : target - start;
|
|
|
|
const perCycle = TOTAL_ANIMATION_TIME / diff;
|
|
|
|
const cyclesCounter = Math.round(Math.abs(TOTAL_ANIMATION_TIME / perCycle));
|
|
|
|
const perCycleTime = Math.round(Math.abs(perCycle));
|
|
|
|
|
|
|
|
return {
|
|
|
|
start,
|
|
|
|
target,
|
|
|
|
cyclesCounter,
|
2016-12-17 19:49:31 +01:00
|
|
|
perCycleTime,
|
2016-12-17 13:53:50 +01:00
|
|
|
increment: diff / cyclesCounter,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
animateTo (percentage, targetState) {
|
|
|
|
cancelAnimationFrame(this.rafCounterTimer);
|
|
|
|
clearTimeout(this.nextTimer);
|
|
|
|
|
|
|
|
const current = this.state.percentageText;
|
|
|
|
|
|
|
|
targetState.cyclesCounter --;
|
|
|
|
if (targetState.cyclesCounter <= 0) {
|
|
|
|
this.setState({ percentageText: targetState.target });
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const next = Math.round(current + targetState.increment);
|
|
|
|
this.rafCounterTimer = requestAnimationFrame(() => {
|
|
|
|
this.setState({ percentageText: next });
|
|
|
|
this.nextTimer = setTimeout(() => {
|
|
|
|
this.animateTo(next, targetState);
|
2016-12-17 19:49:31 +01:00
|
|
|
}, targetState.perCycleTime);
|
2016-12-17 13:53:50 +01:00
|
|
|
});
|
2016-12-04 11:56:41 +01:00
|
|
|
}
|
|
|
|
|
2016-12-17 13:53:50 +01:00
|
|
|
|
2016-12-04 11:56:41 +01:00
|
|
|
componentWillUnmount () {
|
|
|
|
clearTimeout(this.initialTimeout);
|
2016-12-17 13:53:50 +01:00
|
|
|
clearTimeout(this.nextTimer);
|
|
|
|
window.cancelAnimationFrame(this.rafTimerInit);
|
|
|
|
window.cancelAnimationFrame(this.rafCounterTimer);
|
2016-12-04 11:56:41 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
render () {
|
2016-12-17 13:53:50 +01:00
|
|
|
const { strokeWidth } = this.props;
|
2016-12-04 11:56:41 +01:00
|
|
|
const radius = (50 - strokeWidth / 2);
|
|
|
|
const pathDescription = `
|
|
|
|
M 50,50 m 0,-${radius}
|
|
|
|
a ${radius},${radius} 0 1 1 0,${2 * radius}
|
|
|
|
a ${radius},${radius} 0 1 1 0,-${2 * radius}
|
|
|
|
`;
|
|
|
|
|
|
|
|
const diameter = Math.PI * 2 * radius;
|
|
|
|
const progressStyle = {
|
|
|
|
strokeDasharray: `${diameter}px ${diameter}px`,
|
|
|
|
strokeDashoffset: `${((100 - this.state.percentage) / 100 * diameter)}px`,
|
|
|
|
};
|
|
|
|
|
|
|
|
return (<svg viewBox="0 0 100 100">
|
|
|
|
<path
|
|
|
|
className={styles.trail}
|
|
|
|
d={pathDescription}
|
|
|
|
strokeWidth={strokeWidth}
|
|
|
|
fillOpacity={0}
|
|
|
|
/>
|
|
|
|
|
|
|
|
<path
|
|
|
|
className={styles.path}
|
|
|
|
d={pathDescription}
|
|
|
|
strokeWidth={strokeWidth}
|
|
|
|
fillOpacity={0}
|
|
|
|
style={progressStyle}
|
|
|
|
/>
|
|
|
|
|
|
|
|
<text
|
|
|
|
className={styles.text}
|
|
|
|
x={50}
|
|
|
|
y={50}
|
2016-12-17 13:53:50 +01:00
|
|
|
>{this.state.percentageText}%</text>
|
2016-12-04 11:56:41 +01:00
|
|
|
</svg>);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Progress.propTypes = {
|
|
|
|
percentage: PropTypes.number.isRequired,
|
|
|
|
strokeWidth: PropTypes.number,
|
|
|
|
initialAnimation: PropTypes.bool,
|
2016-12-17 13:53:50 +01:00
|
|
|
animatePercentageText: PropTypes.bool,
|
2016-12-04 11:56:41 +01:00
|
|
|
textForPercentage: PropTypes.func,
|
|
|
|
};
|
|
|
|
|
|
|
|
Progress.defaultProps = {
|
|
|
|
strokeWidth: 8,
|
2016-12-17 13:53:50 +01:00
|
|
|
animatePercentageText: false,
|
2016-12-04 11:56:41 +01:00
|
|
|
initialAnimation: false,
|
|
|
|
};
|
|
|
|
|
|
|
|
export default Progress;
|