JSFiddle - React, Tailwind, and code Playground

by mehulkar

HTML

<div data-count="1" id="hi" style="--size: 2.5rem;">0</div>
<button onclick="dothing(this)">add random number</button>

CSS

body {
  margin: 0 auto;
  text-align: center;
}

.vertical-number-wrapper {
  display: flex;
  flex-direction: column;
  overflow: hidden;
  height: 2.5rem;
  line-height: 2.5rem;
  --rotate-val: 0;
  --rotate-delay: 0;
}

.vertical-number {
  transition: transform 0.2s ease-in;
  transition-delay: var(--rotate-delay);
  transform: translateY(var(--rotate-val));
}

#hi {
  font-size: var(--size);
  margin: 0 auto 1rem;
  display: flex;
  align-items: center;
  justify-content: center;
}

JavaScript

function createRotatingDigitElement() {
  const WRAPPER_CLASS = 'vertical-number-wrapper';
  const INDIVIDUAL_NUM_CLASS = 'vertical-number';

  function createDigitOptions() {
    return [0, 1, 2, 3, 4, 5, 6, 7, 8, 9].map((x) => {
      const span = document.createElement('span');
      span.classList.add(INDIVIDUAL_NUM_CLASS);
      span.textContent = x;
      return span;
    });
  }

  const rotatingDigitElement = document.createElement('span');
  rotatingDigitElement.classList.add(WRAPPER_CLASS);
  const digitSpans = createDigitOptions();
  for (const el of digitSpans) {
    rotatingDigitElement.appendChild(el);
  }

  return rotatingDigitElement;
}

function animateUpdateNumber(element, newValue) {
  // update our source of truth in the data attribute
  const currentCount = element.dataset.count;
  element.dataset.count = newValue;

  // replace the textContent with HTML nodes that will do the animation
  // Only do this once, and set a data attribute to indicate that it is "initialized".
  if (!element.dataset.rotateInitialized) {
    element.innerHTML = ''; // clear existing html

    // For each digit, create a rotating digit element and append
    // it to the main element. (replacing the existing HTML)
    for (let i = 0; i < currentCount.length; i++) {
      element.appendChild(createRotatingDigitElement());
    }
    element.dataset.rotateInitialized = true;
  }

  // takes a number, and splits it into digits. e.g. 132 => [1, 3, 2].
  const newValDigits = String(newValue)
    .split('')
    .map((x) => +x);

  // Add more rotating digits if we don't have enough for the newValue.
  // Warning: This assumes that new value will never have fewer digits than the
  // existing number of digits. If we add a subtract button, this could break.
  const numberOfMissingDigits = newValDigits.length - element.childElementCount;
  for (let i = 0; i < numberOfMissingDigits; i++) {
    element.prepend(createRotatingDigitElement());
  }

  // Starting from the end...