4

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

ja23
  • 51
  • 1
  • 5
  • https://stackoverflow.com/questions/55107462/how-to-translate-element-to-act-like-a-odometer This post from years back has logic I think would be useful, but haven't been able to replicate it in React – ja23 Jul 23 '21 at 04:33
  • I have been enjoying this issue for a while. What you want to do is add a 0 to scroll onto the tens place, then teleport back onto the first 0 at the bottom. However this will cause issues when not incrementing by one. Instead what needs to happen is the 0-9 needs to change to current->next values that are scrolled between. I am working on a solution with this in mind and will let you know when I finish. – async await Jul 24 '21 at 02:01
  • @AsyncAwaitFetch thanks!!! I've been hammering away too, and have concluded the same thing as you :D - but I'm not there yet... I think making it smooth will be difficult as well :(. – ja23 Jul 24 '21 at 02:42
  • Just so you know, there are libraries that will do this feature for you without the need to write it yourself in react. However, I enjoy working on the solution. If you fin more let us know. Whenever I have a few free minutes I still look over this issue. I'm having problems with the tens unit moving properly right now, but you can see the progress I have made here: https://codesandbox.io/s/recursing-babbage-22u9j?file=/src/App.js – async await Jul 30 '21 at 06:15
  • did you ever find a solution to this? Recently I came across a [few](https://stackoverflow.com/questions/63785168/css-3d-animated-wheel-off-center) [stack demos](https://stackoverflow.com/questions/30872773/css-only-3d-spinning-text) with 3d css and I was thinking of translating the concepts into react. – async await Dec 16 '21 at 05:06

1 Answers1

2

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>
async await
  • 1,967
  • 1
  • 8
  • 19
  • 1
    Thanks for the demo! I haven't been working in this recently and ended up settling for the 2d, sliding up and down solution that you discussed, and yes it does have issues of being choppy at times. I'm gonna spend some time understanding this, in the future may switch to this. – ja23 Jan 02 '22 at 21:59
  • I guess the one follow up I have is that you mentioned there are lots of libraries that make this trivial. Which ones are you referring to? – ja23 Jan 02 '22 at 22:47
  • It looks more complicated than it is. I got inspiration from [Kevin Powell's demo](https://www.youtube.com/watch?v=NSWr6dkc_Xw). As for libraries that make this trivial, a quick google search of "odometer js" turned up a page full including [this specific](http://jsfiddle.net/adamschwartz/rx6BQ/) odometer [(docs)](https://github.hubspot.com/odometer/) – async await Jan 03 '22 at 08:40