Skip to content

Instantly share code, notes, and snippets.

@vaxxis
Created September 23, 2016 13:28
Show Gist options
  • Select an option

  • Save vaxxis/bdce8eecd726c47160a8c5d4cc000031 to your computer and use it in GitHub Desktop.

Select an option

Save vaxxis/bdce8eecd726c47160a8c5d4cc000031 to your computer and use it in GitHub Desktop.

Revisions

  1. vaxxis created this gist Sep 23, 2016.
    165 changes: 165 additions & 0 deletions AnimateNumber.js
    Original 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)
    }

    }