Search code examples
htmlcss-animations

Spinning number animation on value update


On a site I have a number counter that always increases, without any fancy animation. Now I need to animate it every time the number updates, like this:

enter image description here

Which HTML (+CSS +JS if needed) code can I use for this? Thanks!

P.S.: The number has 2 decimals and uses "," as a decimal separator.


Solution

  • According to the illustration, this already unmaintained library came to mind:

    Example

    const el = document.querySelector('.odometer')
     
    const od = new Odometer({
      el: el,
      value: 1234, // default value
     
      // Any option (other than auto and selector) can be passed in here
      format: '( ddd),dd',
      theme: 'default',
    })
     
    // change to new value
    od.update(9876)
    // or
    el.innerHTML = 9876
    <link rel="stylesheet" href="http://github.hubspot.com/odometer/themes/odometer-theme-default.css" />
    <script src="http://github.hubspot.com/odometer/odometer.js"></script>
    
    <div class="odometer"></div>

    Example with your design

    const values = [4753, 8170]
    const el = document.querySelector('.odometer')
    const od = new Odometer({
      el: el,
      value: values[0], // default value
     
      // Any option (other than auto and selector) can be passed in here
      format: '( ddd),dd',
      theme: 'default',
    })
    
    let index = 1
    function infiniteUpdate() {
      od.update(values[index])
      index = (index + 1) % values.length
      setTimeout(infiniteUpdate, 6000)
    }
    infiniteUpdate()
    @import url('https://fonts.googleapis.com/css2?family=Lexend&display=swap');
    
    body {
      background-color: #6c48ff;
    }
    
    .circle {
      margin: 0 auto;
      height: 150px;
      width: 150px;
      border-radius: 50%;
      background-color: #242400;
      border: 10px solid #fc6caa;
      
      display: flex;
      align-items: center;
      justify-content: center;
      gap: 10px;
      
      color: #fff;
      font-size: 37px;
    }
    
    /* change font-family */
    .circle,
    .odometer-value {
      font-family: 'Lexend', sans-serif;
    }
    
    /* change default duration from 3s to 5s */
    /* https://github.com/HubSpot/odometer/issues/91 */
    .odometer.odometer-animating-up .odometer-ribbon-inner, .odometer.odometer-animating-down.odometer-animating .odometer-ribbon-inner {
        -webkit-transition-duration: 5s !important;
        -moz-transition-duration: 5s !important;
        -ms-transition-duration: 5s !important;
        -o-transition-duration: 5s !important;
        transition-duration: 5s !important;
    }
    
    /* add shadow */
    .odometer::before, .odometer::after {
      content: '';
      position: absolute;
      z-index: 10;
      left: 0;
      width: 100%;
      height: 9px;
    }
    .odometer::before {
      top: 0;
      background: linear-gradient(to bottom, rgba(36, 36, 0, 1), rgba(36, 36, 0, 0));
    }
    .odometer::after {
      bottom: 0;
      background: linear-gradient(to top, rgba(36, 36, 0, 1), rgba(36, 36, 0, 0));
    }
    <link rel="stylesheet" href="http://github.hubspot.com/odometer/themes/odometer-theme-default.css" />
    <script src="http://github.hubspot.com/odometer/odometer.js"></script>
    
    <div class="circle">
      <div>$</div>
      <div class="odometer"></div>
    </div>

    Example with separated fractional part

    A quick idea for the decimal value: You can insert two odometers. The first one counts the whole number, the second one counts the fractional part. This way, you can handle them separately from each other.

    const values = [100.2, 109.2]
    
    const elNumber = document.querySelector('.odometer-number')
    const odNumber = new Odometer({
      el: elNumber,
      value: parseInt(values[0].toString().split('.')[0]), // default value
     
      // Any option (other than auto and selector) can be passed in here
      format: '( ddd)',
      theme: 'default',
    })
    
    const elFract = document.querySelector('.odometer-fractional')
    const odFract = new Odometer({
      el: elFract,
      value: parseInt(values[0].toString().split('.')[1]), // default value
     
      // Any option (other than auto and selector) can be passed in here
      format: 'd',
      theme: 'default',
    })
    
    let index = 1
    function infiniteUpdate() {
      const value = values[index]
      const newNumber = parseInt(value.toString().split('.')[0])
      const newFract = parseInt(value.toString().split('.')[1])
      
      odNumber.update(newNumber)
      odFract.update(newFract)
      
      index = (index + 1) % values.length
      setTimeout(infiniteUpdate, 6000)
    }
    infiniteUpdate()
    @import url('https://fonts.googleapis.com/css2?family=Lexend&display=swap');
    
    body {
      background-color: #6c48ff;
    }
    
    .circle {
      margin: 0 auto;
      height: 150px;
      width: 150px;
      border-radius: 50%;
      background-color: #242400;
      border: 10px solid #fc6caa;
      
      display: flex;
      align-items: center;
      justify-content: center;
      gap: 2px;
      
      color: #fff;
      font-size: 37px;
    }
    
    /* change font-family */
    .circle,
    .odometer-value {
      font-family: 'Lexend', sans-serif;
    }
    
    /* change default duration from 3s to 5s */
    /* https://github.com/HubSpot/odometer/issues/91 */
    .odometer.odometer-animating-up .odometer-ribbon-inner, .odometer.odometer-animating-down.odometer-animating .odometer-ribbon-inner {
        -webkit-transition-duration: 5s !important;
        -moz-transition-duration: 5s !important;
        -ms-transition-duration: 5s !important;
        -o-transition-duration: 5s !important;
        transition-duration: 5s !important;
    }
    
    /* add shadow */
    .odometer::before, .odometer::after {
      content: '';
      position: absolute;
      z-index: 10;
      left: 0;
      width: 100%;
      height: 9px;
    }
    .odometer::before {
      top: 0;
      background: linear-gradient(to bottom, rgba(36, 36, 0, 1), rgba(36, 36, 0, 0));
    }
    .odometer::after {
      bottom: 0;
      background: linear-gradient(to top, rgba(36, 36, 0, 1), rgba(36, 36, 0, 0));
    }
    <link rel="stylesheet" href="http://github.hubspot.com/odometer/themes/odometer-theme-default.css" />
    <script src="http://github.hubspot.com/odometer/odometer.js"></script>
    
    <div class="circle">
      <div>$</div>
      <div class="odometer odometer-number"></div>
      <div>,</div>
      <div class="odometer odometer-fractional"></div>
    </div>