Created
September 23, 2016 13:28
-
-
Save vaxxis/bdce8eecd726c47160a8c5d4cc000031 to your computer and use it in GitHub Desktop.
Revisions
-
vaxxis created this gist
Sep 23, 2016 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,165 @@ 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) } }