Dials

HTML

<script src="demo.js"></script>
<link href="demo.css" rel="stylesheet" />

<div>
  <button id="run" type="button" style="margin-right: 10px;">Run</button>
  <button id="cancel" type="button" disabled>Cancel</button>
</div>

<div id="container">
  <div class="reel">
    <div class="digit">
      <div>0</div>
      <div>1</div>
      <div>2</div>
      <div>3</div>
      <div>4</div>
      <div>5</div>
      <div>6</div>
      <div>7</div>
      <div>8</div>
      <div>9</div>
      <div>0</div>
    </div>
  </div>

  <div class="reel">
    <div class="digit">
      <div>0</div>
      <div>1</div>
      <div>2</div>
      <div>3</div>
      <div>4</div>
      <div>5</div>
      <div>6</div>
      <div>7</div>
      <div>8</div>
      <div>9</div>
      <div>0</div>
    </div>
  </div>

  <div class="reel">
    <div class="digit">
      <div>0</div>
      <div>1</div>
      <div>2</div>
      <div>3</div>
      <div>4</div>
      <div>5</div>
      <div>6</div>
      <div>7</div>
      <div>8</div>
      <div>9</div>
      <div>0</div>
    </div>
  </div>

  <div class="reel">
    <div class="digit">
      <div>0</div>
      <div>1</div>
      <div>2</div>
      <div>3</div>
      <div>4</div>
      <div>5</div>
      <div>6</div>
      <div>7</div>
      <div>8</div>
      <div>9</div>
      <div>0</div>
    </div>
  </div>
</div>

CSS

body {
  font-family: Arial, sans-serif;
}

#container {
  font-size: 60px;
  display: flex;
  width: fit-content;
  padding: 10px;
}

.reel {
  height: 1.1em;
  overflow: hidden;
}

JavaScript

/*
  https://developer.mozilla.org/en-US/docs/Web/API/Element/animate
  https://developer.mozilla.org/en-US/docs/Web/API/KeyframeEffect/KeyframeEffect#parameters
  https://developer.mozilla.org/en-US/docs/Web/API/Animation/finished
*/

/*                                           */
/* Tick-rotate, but also like an odometer.   */
/* we simulate an infinite dial by jumping   */
/* from the last zero in a strip back to the */
/* first zero - see the final keyframes!     */
/*                                           */
function spin() {
  updateButtons();
  unitsAnimation = unitsNode.animate(unitsKeyframes, unitsParameters);
  tensAnimation = tensNode.animate(tensKeyframes, tensParameters);
  hundredsAnimation = hundredsNode.animate(
    hundredsKeyframes,
    hundredsParameters,
  );
  thousandssAnimation = thousandsNode.animate(
    thousandsKeyframes,
    thousandsParameters,
  );
}

// these could be generated on-the-fly, but it's
// much easier to read the hard-coded values (at
// the expense of making them slower to modify):
function buildKeyframes() {
  // units:
  unitsKeyframes = [
    { offset: 0.0, transform: 'translateY(' + height * 0 + 'px)' },
    { offset: 0.05, transform: 'translateY(' + height * -1 + 'px)' },
    { offset: 0.1, transform: 'translateY(' + height * -1 + 'px)' },
    { offset: 0.15, transform: 'translateY(' + height * -2 + 'px)' },
    { offset: 0.2, transform: 'translateY(' + height * -2 + 'px)' },
    { offset: 0.25, transform: 'translateY(' + height * -3 + 'px)' },
    { offset: 0.3, transform: 'translateY(' + height * -3 + 'px)' },
    { offset: 0.35, transform: 'translateY(' + height * -4 + 'px)' },
    { offset: 0.4, transform: 'translateY(' + height * -4 + 'px)' },
    { offset: 0.45, transform: 'translateY(' + height * -5 + 'px)' },
    { offset: 0.5, transform: 'translateY(' + height * -5 + 'px)' },
    { offset: 0.55, transform: 'translateY(' + height * -6 + 'px)' },
    { offset:...