Skip to content

Instantly share code, notes, and snippets.

@vaxxis
Created September 23, 2016 13:28
Show Gist options
  • Save vaxxis/bdce8eecd726c47160a8c5d4cc000031 to your computer and use it in GitHub Desktop.
Save vaxxis/bdce8eecd726c47160a8c5d4cc000031 to your computer and use it in GitHub Desktop.
React Native Animate Number
import React, { Component } from 'react';
import { View, Text } from 'react-native';
import Timer from 'react-timer-mixin';
const HALF_RAD = Math.PI/2
export default class AnimateNumber extends Component {
props : {
countBy? : ?number,
interval? : ?number,
steps? : ?number,
value : number,
timing : 'linear' | 'easeOut' | 'easeIn' | () => number,
formatter : () => {},
onProgress : () => {},
onFinish : () => {}
};
static defaultProps = {
interval : 14,
timing : 'linear',
steps : 45,
value : 0,
formatter : (val) => val,
onFinish : () => {}
};
static TimingFunctions = {
linear : (interval:number, progress:number):number => {
return interval
},
easeOut : (interval:number, progress:number):number => {
return interval * Math.sin(HALF_RAD*progress) * 5
},
easeIn : (interval:number, progress:number):number => {
return interval * Math.sin((HALF_RAD - HALF_RAD*progress)) * 5
},
};
state : {
value? : ?number,
displayValue? : ?number
};
/**
* Animation direction, true means positive, false means negative.
* @type {bool}
*/
direction : bool;
/**
* Start value of last animation.
* @type {number}
*/
startFrom : number;
/**
* End value of last animation.
* @type {number}
*/
endWith : number;
constructor(props:any) {
super(props);
this.dirty = false;
this.startFrom = 0;
this.endWith = 0;
this.state = {
value : 0,
displayValue : 0
};
}
componentDidMount() {
this.startFrom = this.state.value
this.endWith = this.props.value
this.dirty = true
this.startAnimate()
}
componentWillUpdate(nextProps, nextState) {
// check if start an animation
if(this.props.value !== nextProps.value) {
this.startFrom = this.props.value
this.endWith = nextProps.value
this.dirty = true
this.startAnimate()
return
}
// Check if iterate animation frame
if(!this.dirty) {
return
}
if (this.direction === true) {
if(parseInt(this.state.value) <= parseInt(this.props.value)) {
this.startAnimate();
}
}
else if(this.direction === false){
if (parseInt(this.state.value) >= parseInt(this.props.value)) {
this.startAnimate();
}
}
}
render() {
return (
<Text {...this.props}>
{this.state.displayValue}
</Text>)
}
startAnimate() {
let progress = this.getAnimationProgress()
Timer.setTimeout(() => {
let value = (this.endWith - this.startFrom)/this.props.steps
if(this.props.countBy)
value = Math.sign(value)*Math.abs(this.props.countBy)
let total = parseInt(this.state.value) + parseInt(value)
this.direction = (value > 0)
// animation terminate conditions
if (((this.direction) ^ (total <= this.endWith)) === 1) {
this.dirty = false
total = this.endWith
this.props.onFinish(total, this.props.formatter(total))
}
if(this.props.onProgress)
this.props.onProgress(this.state.value, total)
this.setState({
value : total,
displayValue : this.props.formatter(total)
})
}, this.getTimingFunction(this.props.interval, progress))
}
getAnimationProgress():number {
return (this.state.value - this.startFrom) / (this.endWith - this.startFrom)
}
getTimingFunction(interval:number, progress:number) {
if(typeof this.props.timing === 'string') {
let fn = AnimateNumber.TimingFunctions[this.props.timing]
return fn(interval, progress)
} else if(typeof this.props.timing === 'function')
return this.props.timing(interval, progress)
else
return AnimateNumber.TimingFunctions['linear'](interval, progress)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment