JSFiddle - React, Tailwind, and code Playground

by Mladen Mihajlovic

HTML

<div id="odometer">
  <div class="digit">0</div>
  <div class="digit">0</div>
  <div class="digit">0</div>
</div>

CSS

#odometer {
  display: flex;
  justify-content: center;
  align-items: center;
  width: 150px;
  height: 50px;
  background-color: #eee;
  font-size: 24px;
  font-weight: bold;
}

.digit {
  position: relative;
  width: 30px;
  height: 50px;
  margin-right: 5px;
  overflow: hidden;
}

.digit:before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background-color: #fff;
  transform: translateY(0);
  transition: transform 0.5s ease-out;
}

.digit:after {
  content: "";
  position: absolute;
  top: -100%;
  left: 0;
  width: 100%;
  height: 100%;
  background-color: #fff;
}

@keyframes scroll {
  0% {
    transform: translateY(0);
  }
  100% {
    transform: translateY(-300%);
  }
}

.digit.animate:before {
  animation: scroll 1.5s ease-out forwards;
}

JavaScript

function scrollNumber(number) {
  const digits = document.querySelectorAll("#odometer .digit");
  const numbers = Array.from(String(number), Number);

  digits.forEach((digit, i) => {
    if (numbers[i] !== undefined) {
      digit.classList.add("animate");
      const target = digit.querySelector(":before");
      target.style.transform = `translateY(-${numbers[i] * 50}%)`;
    } else {
      digit.classList.remove("animate");
    }
  });

  // Wait for animation to finish before resetting
  setTimeout(() => {
    digits.forEach((digit) => {
      digit.classList.remove("animate");
      const target = digit.querySelector(":before");
      target.style.transform = `translateY(0)`;
    });
  }, 1500);
}

scrollNumber(123);