Last active
July 7, 2016 13:30
-
-
Save cevek/669da50cc68fc24ec37d07812997ebba to your computer and use it in GitHub Desktop.
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 characters
import './AnimatedNumber.scss'; | |
import React from 'react'; | |
export class AnimatedNumber extends React.Component { | |
number; | |
nextNumber; | |
static mountElements = 0; | |
static vendorTransform; | |
static defaultProps = { | |
height: 18, | |
digitsCount: 0, | |
}; | |
constructor(props) { | |
super(props); | |
this.number = props.number; | |
this.nextNumber = this.number; | |
} | |
componentWillReceiveProps(nextProps) { | |
this.number = this.nextNumber; | |
this.nextNumber = nextProps.number; | |
} | |
getSym(str, max, pos, def) { | |
return (max - str.length) > pos ? def : str[str.length - max + pos]; | |
} | |
findProp(style, props) { | |
for (let i = 0; i < props.length; i++) { | |
if (style[props[i]] !== undefined) { | |
return props[i]; | |
} | |
} | |
throw new Error('prop not found'); | |
} | |
transitionEnd(e) { | |
const node = e.target; | |
if (node.hasAttribute('data-animate')) { | |
node.removeAttribute("data-animate"); | |
node.style.transform = ''; | |
node.textContent = node.$textContent; | |
node.parentNode.style.height = ''; | |
} | |
} | |
componentWillMount() { | |
if (AnimatedNumber.mountElements++ == 0) { | |
AnimatedNumber.vendorTransform = this.findProp(document.body.style, ['transform', 'webkitTransform', 'mozTransform', 'msTransform']); | |
document.body.addEventListener('transitionend', this.transitionEnd); | |
document.body.addEventListener('webkitTransitionEnd', this.transitionEnd); | |
} | |
} | |
componentWillUnmount() { | |
if (--AnimatedNumber.mountElements == 0) { | |
document.body.removeEventListener('transitionend', this.transitionEnd); | |
document.body.removeEventListener('webkitTransitionEnd', this.transitionEnd); | |
} | |
} | |
getNumberDigits() { | |
// console.log('Render'); | |
const {vendorTransform} = AnimatedNumber; | |
const numStr = this.nextNumber + ""; | |
const oldStr = this.number + ""; | |
const max = Math.max(numStr.length, oldStr.length, this.props.digitsCount); | |
const globFromTop = +this.nextNumber > +this.number; | |
const digits = new Array(max); | |
const notANumber = (this.number + "" !== +this.number + "") || (this.nextNumber + "" !== +this.nextNumber + ""); | |
const oldSign = this.number > 0 ? 1 : -1; | |
const newSign = this.nextNumber > 0 ? 1 : -1; | |
for (let i = 0; i < max; i++) { | |
const digit = this.getSym(numStr, max, i, ''); | |
const oldDigit = this.getSym(oldStr, max, i, ''); | |
const nonDigit = (oldDigit + "" !== +oldDigit + "") || (digit + "" !== +digit + ""); | |
let from = nonDigit ? 0 : +oldDigit * oldSign; | |
let to = nonDigit ? 0 : +digit * newSign; | |
if (globFromTop) { | |
to = from > to ? to + 10 : to; | |
} else { | |
from = from < to ? from + 10 : from; | |
} | |
const count = nonDigit ? 1 : Math.abs(from - to); | |
const topPercent = (count) / (count + 1) * 100; | |
const fromTop = globFromTop; | |
const digitNode = this.refs ? this.refs['digit' + i] : null; | |
// do not remove, need for debug | |
/*console.log({ | |
from, | |
to, | |
digit, | |
oldDigit, | |
nonDigit, | |
height, | |
numStr, | |
oldStr, | |
fromTop, | |
i, | |
digitNode, | |
str: this.generateText(fromTop ? from : to, fromTop ? to : from), | |
cmp: this | |
});*/ | |
if (digit !== oldDigit) { | |
if (digitNode) { | |
const nonDigitStr = fromTop ? digit + '\n' + oldDigit + '\n' : oldDigit + '\n' + digit + '\n'; | |
digitNode.textContent = nonDigit ? nonDigitStr : this.generateText(fromTop ? from : to, fromTop ? to : from); | |
digitNode.$textContent = digit; | |
digitNode.style[vendorTransform] = `translateY(-${fromTop ? topPercent : 0}%)`; | |
digitNode.offsetHeight; | |
digitNode.setAttribute("data-animate", ""); | |
digitNode.style[vendorTransform] = `translateY(-${fromTop ? 0 : topPercent}%)`; | |
} | |
} | |
digits[i] = <div ref={'digit' + i} className="digit">{digit}</div>; | |
} | |
return digits; | |
} | |
generateText(from, to) { | |
let s = ''; | |
for (let i = to; i >= from; i--) { | |
s += Math.abs(i % 10) + '\n'; | |
} | |
return s; | |
} | |
render() { | |
const digits = this.getNumberDigits(); | |
return <div className="animated-number">{digits}</div> | |
} | |
} | |
// Do not remove, need for test AnimatedNumbers | |
export class MuchNumber extends React.Component { | |
// [from, to, slotsCount] | |
numbersSet = [[8, 9], [9, 10], [8, 9, 3], [9, 10, 3], [-1, 0], [-35, 7], [0, -1], [-735, -27], [1.1, 1.3], [-2.7, -3.2], [1.1, 1.41]]; | |
// numbersSet = [[0, -1, 2]]; | |
update(useFrom) { | |
this.numbers = this.numbersSet.map(([from, to, count]) => [useFrom ? from : to, count]); | |
this.forceUpdate(); | |
setTimeout(()=>this.update(!useFrom), 5000); | |
}; | |
constructor(props) { | |
super(props); | |
this.numbers = this.numbersSet.map(([from, to, count]) => [from, count]); | |
setTimeout(()=>this.update(false), 1000); | |
} | |
render() { | |
return <div ref="root"> | |
{this.numbers.map(([num, count]) => <div><AnimatedNumber digitsCount={count} number={num}/></div>)} | |
</div> | |
} | |
} | |
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 characters
.animated-number { | |
display: inline-block; | |
overflow: hidden; | |
height: 1em; | |
vertical-align: middle; | |
.digit { | |
display: inline-block; | |
white-space: pre; | |
vertical-align: top; | |
} | |
.digit[data-animate] { | |
transition: transform 3s ease; | |
} | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment