Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Translating div to behave like odometer

I've been trying to create an odometer like animation using React, and vanilla css. So far it's working where when number is incremented, a translationY upwards occurs like an actual odometer. My current problem is that when it goes from 9 to 0, the translationY occurs in the opposite direction (downwards instead of upwards). I would like for it to still go in the same direction (up) but super stuck on how to do this. Any help would be greatly appreciated...

Code is here: https://codesandbox.io/s/inspiring-ellis-jpzx2

like image 836
ja23 Avatar asked Sep 10 '25 15:09

ja23


1 Answers

I spent way too much time looking at solutions for this. First off, there are a ton of libraries that would make something like this trivial, however I enjoyed the learning process of finding a solution.

I did not create a react specific solution, but my vanilla javascript demo should be more than enough to easily port it into a react solution.

To accomplish this task I first tried to create an element each time there was a change, with the bottom number being the starting number, and the top number being the landing number, and slide it down until the desired number was hit, and make the old element disappear. However this ended up looking choppy and had some unintended effects when the element was changed rapidly.

After stumbling across a few demos, I realized that 3d css might be the perfect solution. Instead of having a 2d element we transition up and down, we could create a 3d element that was spinning on a wheel. Js could calculate the degree needed for rotating the element to always be spinning forward.

Please enjoy my small demo, and if you have any questions please ask.

const $ = (str, dom = document) => [...dom.querySelectorAll(str)];

const panels = $(".panel");
panels.forEach((panel, i) => {
  panel.style.setProperty("--angle", `${360 / panels.length * i}deg`)
});
const ring = $(".ring")[0];
const nums = $(".num");
nums.forEach((num, i) => {
  num.addEventListener("click", () => {
    const numAngle = 36 * i;
    const currentAngle =
      ring.style.getPropertyValue("--deg")
      .replace(/\D/g, "");
    let nextAngle = numAngle;
    while (nextAngle < currentAngle) {
      nextAngle += 360;
    }
    ring.style.setProperty("--deg", `-${nextAngle}deg`)
  });
});
* {
 margin: 0;
 padding: 0;
 box-sizing: border-box;
 font-family: monospace;
}

body {
 display: grid;
 place-items: center;
 min-height: 100vh;
 perspective: 500px;
 perspective-origin: 50% 50%;
}

.ring {
  transform-style: preserve-3d;
  transition: transform 1s;
  transform: rotateX(var(--deg));
}

.panel {
  position: absolute;
  transform:
    translate(-50%, -50%)
    rotateX(var(--angle))
    translateZ(22.5px);
  border-bottom: 1px solid black;
  background-color: white;
}

.numPanel {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  display: flex;
  justify-content: center;
  gap: 1rem;
  user-select: none;
}

.num {
  font-size: 2rem;
  padding: 0.5rem;
  cursor: pointer;
}

.num:hover {
  background-color: lightblue;
}
<div class="numPanel">
  <div class="num">0</div>
  <div class="num">1</div>
  <div class="num">2</div>
  <div class="num">3</div>
  <div class="num">4</div>
  <div class="num">5</div>
  <div class="num">6</div>
  <div class="num">7</div>
  <div class="num">8</div>
  <div class="num">9</div>
</div>
<div class="ring">
  <div class="panel">0</div>
  <div class="panel">1</div>
  <div class="panel">2</div>
  <div class="panel">3</div>
  <div class="panel">4</div>
  <div class="panel">5</div>
  <div class="panel">6</div>
  <div class="panel">7</div>
  <div class="panel">8</div>
  <div class="panel">9</div>
</div>
like image 144
async await Avatar answered Sep 13 '25 06:09

async await