Retro Mechanical Counter In JavaScript - MalikDzgn
  • Jelajahi

    Copyright © MalikDzgn -

    mardi 16 février 2021

    Retro Mechanical Counter In JavaScript

    Retro Mechanical Counter In JavaScript

     

    Retro Mechanical Counter In JavaScript

    Category: Javascript | August 31, 2020
    AUTHOR:Andrew Rubin
    VIEWS TOTAL:527 views
    OFFICIAL PAGE:Go to website
    LAST UPDATE:August 31, 2020
    LICENSE:MIT

    Preview:

    Retro Mechanical Counter In JavaScript

    Description:

    A retro number counter that counts up to a specified number with a digit flipping effect just like a mechanical counter.

    Built with vanilla JavaScript and CSS3 animations/transforms.

    How to use it:

    1. Create an empty container for the counter.

    <div class="cool-element"></div>

    2. Create an input field to accept the number you’d like to count up to.

    <input class="number-input" type="number" value="123" />
    <button class="number-button">Start The Counter</button>

    3. The main JavaScript to enable the counter.

    const element = document.querySelector(".cool-element"),
    numberInput = document.querySelector(".number-input"),
    numberSubmit = document.querySelector(".number-button");
    const ROOT_CLASS_NAME = "digit-flipper";
    class DigitFlipper {
      constructor(element, options = {
        number: 9,
        iterationCount: 9 })
      {
        // First, some parameter sanitizing:
        if (options.number > 9 || options.number < 0) return;
        this.options = Object.assign({}, options);
        if (!this.options.number) this.options.number = 9;
        if (!this.options.iterationCount) this.options.iterationCount = 9;
        // Adjusting the number of iterations,
        // in case our numbers end up in the negatives:
        if (this.options.number - this.options.iterationCount < 0) {
          this.options.iterationCount = this.options.number;
        }
        this.element = element;
        this.digitClassName = `${ROOT_CLASS_NAME}__digit`;
        this.topClassName = `${this.digitClassName}-top`;
        this.bottomClassName = `${this.digitClassName}-bottom`;
        this.flipTopClass = `${this.digitClassName}--flip-top`;
        this.flipBottomClass = `${this.digitClassName}--flip-bottom`;
        this.flipDoneClass = `${this.digitClassName}--flip-done`;
        this.DOMNodes = [];
        this.flipDuration = parseFloat(
        (window.getComputedStyle(document.documentElement).
        getPropertyValue("--flip-duration") || "1s").
        replace("s", ""));
        this._init();
        return this;
      }
      _init() {
        this._populateDOM();
      }
      // creates DOM elements for each digit and all of its "iterations"
      _populateDOM() {
        let i = this.options.number - this.options.iterationCount;
        for (i; i <= this.options.number; i++) {
          const digit = document.createElement("span"),
          digitTop = document.createElement("span"),
          digitBottom = document.createElement("span"),
          digitText = document.createTextNode(i);
          digit.className = this.digitClassName;
          digitTop.className = this.topClassName;
          digitBottom.className = this.bottomClassName;
          digitTop.appendChild(digitText);
          digitBottom.appendChild(digitText.cloneNode());
          digit.appendChild(digitTop);
          digit.appendChild(digitBottom);
          this.DOMNodes.push(digit);
          this.element.insertAdjacentElement("afterbegin", digit);
        }
      }
      // runs the animtion sequence for the digit
      flip() {
        this.DOMNodes.forEach((node, index) => {
          const nextNode = this.DOMNodes[index + 1];
          let delay = this.flipDuration * index * 1000;
          // The flipBottomClass turns the bottom half
          // down from it's inital state of 90deg
          // The flipTopClass turns the top half
          // down from it's inital state of 0deg
          const t1 = setTimeout(() => {
            node.classList.add(this.flipBottomClass);
            clearTimeout(t1);
            const t2 = setTimeout(() => {
              if (nextNode) node.classList.add(this.flipTopClass);
              clearTimeout(t2);
              const t3 = setTimeout(() => {
                node.style.zIndex = index + 1;
                clearTimeout(t3);
              }, this.flipDuration);
            }, this.flipDuration);
          }, delay);
        });
      }}
    class FlipCounter {
      constructor(element, value) {
        if (typeof value !== "number") return;
        this.element = element;
        this.targetNumber = value;
        this.targetDigits = [];
        this.numDigits = this.targetNumber.toString().length;
        this.DOMNodes = [];
        this.flipperInstances = [];
        // separate the digits of the value arg
        for (let i = 0; i < this.numDigits; i++) {
          this.targetDigits.push(this.targetNumber.toString()[i]);
        }
        this.populateDOM();
        this.populateInstanceArray();
      }
      // creates wrapper elements for each digit
      populateDOM() {
        this.element.innerHTML = "";
        let i = 0;
        for (i; i < this.numDigits; i++) {
          const container = document.createElement("span");
          container.className = ROOT_CLASS_NAME;
          this.element.appendChild(container);
          this.DOMNodes.push(container);
        }
      }
      // instantiate a DigitFlipper object for each digit
      populateInstanceArray() {
        this.DOMNodes.forEach((digit, index) => {
          this.flipperInstances.push(
          new DigitFlipper(digit, {
            number: this.targetDigits[index],
            iterationCount: 4 }));
        });
      }
      // runs the animation, with a 200ms stagger
      play() {
        this.flipperInstances.forEach((instance, index) => {
          let delay = index * 200;
          setTimeout(() => instance.flip(), delay);
        });
      }}
    // Handles the input field, for the demo
    const onClick = () => {
      let num = Number(numberInput.value);
      if (num >= 0) {
        let counter = new FlipCounter(element, num);
        counter.play();
      }
    };
    numberSubmit.addEventListener("click", onClick);
    // kick off the initial one
    const counter = new FlipCounter(element, Number(numberInput.value));
    counter.play();

    4. The necessary CSS/CSS3 rules for the digit flipping effect.

    .digit-flipper {
      display: inline-block;
      height: 0.98em;
      font-size: 20vmin;
      line-height: 1;
      margin: 0 0.02em;
      -webkit-perspective: 300px;
              perspective: 300px;
      position: relative;
      width: 0.65em;
    }
    
    .digit-flipper__digit {
      display: block;
      height: 100%;
      position: absolute;
      text-align: center;
      width: 100%;
    }
    
    .digit-flipper__digit-top,
    .digit-flipper__digit-bottom {
      color: black;
      display: block;
      height: 100%;
      position: absolute;
      -webkit-transform-origin: 50% 50%;
              transform-origin: 50% 50%;
      width: 100%;
    }
    
    .digit-flipper__digit-top {
      background-color: #ffffff;
      border-top-left-radius: 10px;
      border-top-right-radius: 10px;
      -webkit-clip-path: inset(0 0 51% 0);
              clip-path: inset(0 0 51% 0);
      overflow: hidden;
      -webkit-transform: rotateX(0deg);
              transform: rotateX(0deg);
    }
    
    .digit-flipper__digit-bottom {
      background-color: #d9d9d9;
      border-bottom-left-radius: 10px;
      border-bottom-right-radius: 10px;
      -webkit-clip-path: inset(51% 0 0 0);
              clip-path: inset(51% 0 0 0);
      -webkit-transform: rotateX(90deg);
              transform: rotateX(90deg);
    }
    
    .digit-flipper__digit--flip-bottom .digit-flipper__digit-bottom {
      -webkit-animation: flip-bottom .3s ease-in 0s 1 forwards;
              animation: flip-bottom .3s ease-in 0s 1 forwards;
    }
    
    .digit-flipper__digit--flip-top .digit-flipper__digit-top {
      -webkit-animation: flip-top .3s ease-in 0s 1 forwards;
              animation: flip-top .3s ease-in 0s 1 forwards;
    }
    
    .digit-flipper__digit--flip-done .digit-flipper__digit-bottom {
      -webkit-transform: rotateX(0deg);
              transform: rotateX(0deg);
    }
    
    @-webkit-keyframes flip-top {
      from {
        -webkit-transform: rotateX(0deg);
                transform: rotateX(0deg);
      }
      to {
        -webkit-transform: rotateX(-90deg);
                transform: rotateX(-90deg);
      }
    }
    
    @keyframes flip-top {
      from {
        -webkit-transform: rotateX(0deg);
                transform: rotateX(0deg);
      }
      to {
        -webkit-transform: rotateX(-90deg);
                transform: rotateX(-90deg);
      }
    }
    
    @-webkit-keyframes flip-bottom {
      from {
        -webkit-transform: rotateX(90deg);
                transform: rotateX(90deg);
      }
      to {
        -webkit-transform: rotateX(0deg);
                transform: rotateX(0deg);
      }
    }
    
    @keyframes flip-bottom {
      from {
        -webkit-transform: rotateX(90deg);
                transform: rotateX(90deg);
      }
      to {
        -webkit-transform: rotateX(0deg);
                transform: rotateX(0deg);
      }
    }
    
    .cool-element {
      background-color: #404040;
      border-radius: 15px;
      box-shadow: inset -2px -2px 10px 0px black, inset 4px 4px 10px 0px rgba(255, 255, 255, 0.4), 5px 5px 15px 0px rgba(0, 0, 0, 0.3);
      margin: -40px 0 auto;
      padding: 20px 18px 16px;
    }

    Share with your friends

    Give us your opinion
    Show Comments
    Close Comment