animated_number.tsx 1.5 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162
  1. import { useCallback, useState } from 'react';
  2. import { TransitionMotion, spring } from 'react-motion';
  3. import { reduceMotion } from '../initial_state';
  4. import { ShortNumber } from './short_number';
  5. interface Props {
  6. value: number;
  7. }
  8. export const AnimatedNumber: React.FC<Props> = ({ value }) => {
  9. const [previousValue, setPreviousValue] = useState(value);
  10. const [direction, setDirection] = useState<1 | -1>(1);
  11. if (previousValue !== value) {
  12. setPreviousValue(value);
  13. setDirection(value > previousValue ? 1 : -1);
  14. }
  15. const willEnter = useCallback(() => ({ y: -1 * direction }), [direction]);
  16. const willLeave = useCallback(
  17. () => ({ y: spring(1 * direction, { damping: 35, stiffness: 400 }) }),
  18. [direction],
  19. );
  20. if (reduceMotion) {
  21. return <ShortNumber value={value} />;
  22. }
  23. const styles = [
  24. {
  25. key: `${value}`,
  26. data: value,
  27. style: { y: spring(0, { damping: 35, stiffness: 400 }) },
  28. },
  29. ];
  30. return (
  31. <TransitionMotion
  32. styles={styles}
  33. willEnter={willEnter}
  34. willLeave={willLeave}
  35. >
  36. {(items) => (
  37. <span className='animated-number'>
  38. {items.map(({ key, data, style }) => (
  39. <span
  40. key={key}
  41. style={{
  42. position: direction * style.y > 0 ? 'absolute' : 'static',
  43. transform: `translateY(${style.y * 100}%)`,
  44. }}
  45. >
  46. <ShortNumber value={data as number} />
  47. </span>
  48. ))}
  49. </span>
  50. )}
  51. </TransitionMotion>
  52. );
  53. };