Skip to content

Instantly share code, notes, and snippets.

@cevek
Last active July 7, 2016 13:30
Show Gist options
  • Save cevek/669da50cc68fc24ec37d07812997ebba to your computer and use it in GitHub Desktop.
Save cevek/669da50cc68fc24ec37d07812997ebba to your computer and use it in GitHub Desktop.
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>
}
}
.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