2024-09-09 14:29:40 -07:00

136 lines
2.9 KiB
JavaScript

import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
const propTypes = {
from: PropTypes.number,
to: PropTypes.number.isRequired,
speed: PropTypes.number.isRequired,
delay: PropTypes.number,
onComplete: PropTypes.func,
digits: PropTypes.number,
className: PropTypes.string,
tagName: PropTypes.string,
children: PropTypes.func,
easing: PropTypes.func,
position: PropTypes.shape({
height: PropTypes.number,
startY: PropTypes.number,
}),
};
const defaultProps = {
from: 0,
delay: 100,
digits: 0,
tagName: 'span',
easing: t => t,
};
class CountTo extends PureComponent {
constructor(props) {
super(props);
const { from } = props;
this.state = {
counter: from,
restart: false
};
this.start = this.start.bind(this);
this.clear = this.clear.bind(this);
this.next = this.next.bind(this);
this.updateCounter = this.updateCounter.bind(this);
}
componentDidMount() {
this.start();
window.addEventListener('scroll', () => {
if (!this.props.position) return;
const { from, to } = this.props.position;
if (window.scrollY > from && window.scrollY < to && this.state.restart) {
this.start();
this.setState({ restart: false });
}
if (window.scrollY < from && !this.state.restart) {
this.setState({ restart: true });
}
});
}
componentWillUnmount() {
this.clear();
}
start(props = this.props) {
this.clear();
const { from } = props;
this.setState({
counter: from,
}, () => {
const { speed, delay } = this.props;
const now = Date.now();
this.endDate = now + speed;
this.scheduleNextUpdate(now, delay);
this.raf = requestAnimationFrame(this.next);
});
}
next() {
const now = Date.now();
const { speed, onComplete, delay } = this.props;
if (now >= this.nextUpdate) {
const progress = Math.max(0, Math.min(1, 1 - (this.endDate - now) / speed));
this.updateCounter(progress);
this.scheduleNextUpdate(now, delay);
}
if (now < this.endDate) {
this.raf = requestAnimationFrame(this.next);
} else if (onComplete) {
onComplete();
}
}
scheduleNextUpdate(now, delay) {
this.nextUpdate = Math.min(now + delay, this.endDate);
}
updateCounter(progress) {
const { from, to, easing } = this.props;
const delta = to - from;
const counter = from + delta * easing(progress);
this.setState({
counter,
});
}
clear() {
cancelAnimationFrame(this.raf);
}
render() {
const { className, digits, tagName: Tag, children: fn } = this.props;
const { counter } = this.state;
const value = counter.toFixed(digits);
if (fn) {
return fn(value);
}
return (
<Tag className={className}>
{value}
</Tag>
);
}
}
CountTo.propTypes = propTypes;
CountTo.defaultProps = defaultProps;
export default CountTo;