Search code examples
vue.jstweenjsnumeral.js

Using Vue with Tween JS makes tween sometimes reset


I'm trying to animate a counter that I've made with Vue.js.

The value is being animated by Tween.js and then formatted with Numeral js but I'm having an issue.

I'd like to increment by a random amount, and when I increment, animate the number from whatever it is currently showing to the new total.

I have num which holds the final value of the counter, and displayNum which holds the animating value.

The issue is that my current code (if you hammer the increment button) sometimes starts animating the number from 0, rather than the current total. Especially when you get to over 1,000.

How can I stop this behaviour?

At present, whenever you click increment it takes the currently displayed number and starts a new tween to the target number.

function animate(time) {
    requestAnimationFrame(animate);
    TWEEN.update(time);
}
requestAnimationFrame(animate);

var v = new Vue({
  'el' : '#app',
  'data' : {
    num : 0,
    displayNum : 0,
    tween : false
  },
  methods:{
    increment(){
      var vm = this;
      // select a random number and add it to our target
      vm.num += Math.round(Math.random() * 300);
      // create an object that we can use tween.js to animate
      var anim = { num: vm.displayNum };
      // if we are already animating, stop the animation
      if( vm.tween ){
        vm.tween.stop();
      }
      // create a new animation from the current number
      vm.tween = new TWEEN.Tween(anim)
        // to our new target
        .to({ num : vm.num }, 3000)
        .easing(TWEEN.Easing.Quadratic.Out)
        .onUpdate(function() {
        
          // Failed attempt to debug
          if( anim.num < vm.displayNum ){
            console.log("Something isn't right");
          }
          // on update, replace the display number with a rounded, formatted 
          // version of the animating number
          vm.displayNum = numeral(Math.round(anim.num)).format('0,0');
        })
        // if the tween ever stops, set the vm's current tween to 
        // false
        .onStop(function(){
          vm.tween = false;
        })
        .start();
    }
  }
})
html, body{
  height:100%;
}
body{
  display:flex;
  align-items:center;
  justify-content:center;
}
.window{
  width:200px;
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.6.1/css/bulma.min.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/numeral.js/2.0.6/numeral.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/tween.js/r14/Tween.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.3/vue.min.js"></script>


<div id="app">
  <div class="window has-text-centered">
    <div class="is-size-1">
      <span v-html="displayNum"></span>
    </div>
    <div>
      <button class="button" @click="increment">Increment</button>
    </div>
    <div class="is-size-7">
      <span v-html="num"></span>
    </div>
  </div>
</div>


Solution

  • The problem is you are mixing strings and integers up. First, make sure displayNum is a string on the model and then when you initialize the animation make sure to parseInt it while removing the comma that shows up when you go over 999.

    function animate(time) {
        requestAnimationFrame(animate);
        TWEEN.update(time);
    }
    requestAnimationFrame(animate);
    
    var v = new Vue({
      'el' : '#app',
      'data' : {
        num : 0,
        displayNum : "0",
        tween : false
      },
      methods:{
        increment(){
          var vm = this;
          // select a random number and add it to our target
          vm.num += Math.round(Math.random() * 300);
          // create an object that we can use tween.js to animate
          var anim = { num: parseInt(vm.displayNum.replace(",","")) };
          // if we are already animating, stop the animation
          if( vm.tween ){
            vm.tween.stop();
          }
          // create a new animation from the current number
          vm.tween = new TWEEN.Tween(anim)
            // to our new target
            .to({ num : vm.num }, 3000)
            .easing(TWEEN.Easing.Quadratic.Out)
            .onUpdate(function() {
            
              // Failed attempt to debug
              //if( anim.num < vm.displayNum ){
              //  console.log("Something isn't right", anim, vm.displayNum);
              //}
              // on update, replace the display number with a rounded, formatted 
              // version of the animating number
              vm.displayNum = numeral(Math.round(anim.num)).format('0,0');
            })
            // if the tween ever stops, set the vm's current tween to 
            // false
            .onStop(function(){
              vm.tween = false;
            })
            .start();
        }
      }
    })
    html, body{
      height:100%;
    }
    body{
      display:flex;
      align-items:center;
      justify-content:center;
    }
    .window{
      width:200px;
    }
    <link href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.6.1/css/bulma.min.css" rel="stylesheet"/>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/numeral.js/2.0.6/numeral.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/tween.js/r14/Tween.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.3/vue.min.js"></script>
    
    
    <div id="app">
      <div class="window has-text-centered">
        <div class="is-size-1">
          <span v-html="displayNum"></span>
        </div>
        <div>
          <button class="button" @click="increment">Increment</button>
        </div>
        <div class="is-size-7">
          <span v-html="num"></span>
        </div>
      </div>
    </div>