1

I have a Vue2 project with a simple component displaying scores from two teams. This component is able to take the current score and the score from the last game as props (both numbers will always be positive integers). So it's easy to calculate the previous scores. Instead of displaying the current score directly I would like to add a count up animation.

This is what I have so far

<template>
  <div>{{ firstTeamCounterValue }} - {{ secondTeamCounterValue }}</div>
</template>

<script>
export default {
  props: {
    firstTeamCurrentScore: {
      type: Number,
      default: 0,
    },
    secondTeamCurrentScore: {
      type: Number,
      default: 0,
    },
    firstTeamMapScore: {
      type: Number,
      default: 0,
    },
    secondTeamMapScore: {
      type: Number,
      default: 0,
    },
  },
  data() {
    return {
      firstTeamCounterValue: 0,
      secondTeamCounterValue: 0,
    };
  },
  created() { // do this for the first run
    this.countUpScores();
  },
  updated() { // do this whenever the props change (score changed)
    this.countUpScores();
  },
  methods: {
    countUpScores() {
      this.firstTeamCounterValue = this.firstTeamPreviousScore;
      this.secondTeamCounterValue = this.secondTeamPreviousScore;

      const countUpInterval = setInterval(() => {
        const firstTeamCurrentScoreReached = this.firstTeamCounterValue === this.firstTeamCurrentScore;
        const secondTeamCurrentScoreReached = this.secondTeamCounterValue === this.secondTeamCurrentScore;

        if (firstTeamCurrentScoreReached && secondTeamCurrentScoreReached) {
          // stop
          clearInterval(countUpInterval);
        } else {
          if (!firstTeamCurrentScoreReached) {
            this.firstTeamCounterValue += 1;
          }

          if (!secondTeamCurrentScoreReached) {
            this.secondTeamCounterValue += 1;
          }
        }
      }, 200);
    },
  },
  computed: {
    firstTeamPreviousScore() {
      return this.firstTeamCurrentScore - this.firstTeamMapScore;
    },
    secondTeamPreviousScore() {
      return this.secondTeamCurrentScore - this.secondTeamMapScore;
    },
  },
};
</script>

When running the application I pass in the initial scores. Later on the props change. Unfortunately simply nothing happens. There is no count up animation.

Does someone got an idea how to make it work?

I know a little bit of CSS. If you think I shouldn't use code for this please let me know! But I don't know how I would trigger the CSS animation and pass in the required values whenever the update hook was triggered.

Question3r
  • 2,166
  • 19
  • 100
  • 200

2 Answers2

1

Here's what you need:

  • A displayedCount data property that "follows" a count prop
  • A change method that...
    • ...increment or decrement displayedCount based on whether count is above or below it
    • ...calls itself recursively
    • ...does not call itself when displayedCount has reached count
  • A call to the change method when the element is created
  • A call to the change methods whenever the count prop update

You might find ways to make it work with setInterval, but I find that a recursive setTimeout is better here.

Here is some working code. Update the number value in the input and observe the number displayed by Count make a count-up animation:

Vue.config.productionTip = false;

const Counter = {
  template: `<span>{{ displayedCount }}</span>`,
  props: ['count'],
  data() {
    return {
      displayedCount: 0
    }
  },
  created() {
    this.change();
  },
  watch: {
    count() {
      this.change();
    }
  },
  methods: {
    change() {
      if (this.displayedCount === this.count) return;
      if (this.displayedCount < this.count) this.displayedCount++;
      if (this.displayedCount > this.count) this.displayedCount--;
      setTimeout(() => this.change(), 200);
    }
  }
};

const App = new Vue({
  el: '#app',
  components: { Counter },
  template: `
    <div>
      <input type="number" v-model="count">
      <Counter :count="count"/>
    </div>
  `,
  data() {
    return {
      count: 0
    }
  }
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>

<div id="app"></div>

You asked in your question to have one component keep track of two scores. I find it more elegant to have one Count component keeping track of one score and possibly another Counts component keeping track of two scores using two Count components internally. This part is trivial and so I'll let you do it ;)

Nino Filiu
  • 16,660
  • 11
  • 54
  • 84
0

You could watch your props and have a continuing interval that increment your counter values only when they need to be.

<template>
  <div>{{ firstTeamCounterValue }} - {{ secondTeamCounterValue }}</div>
</template>
 
<script>
export default {
  props: {
    firstTeamCurrentScore: {
      type: Number,
      default: 0,
    },
    secondTeamCurrentScore: {
      type: Number,
      default: 0,
    },
    firstTeamMapScore: {
      type: Number,
      default: 0,
    },
    secondTeamMapScore: {
      type: Number,
      default: 0,
    },
  },
  data() {
    return {
      firstTeamCounterValue: 0,
      secondTeamCounterValue: 0,
      firstValueToReach: 0,
      secondValueToReach: 0,
      countInterval: null,
    };
  },
  mounted() {
    this.firstValueToReach = this.firstTeamCurrentScore;
    this.secondValueToReach = this.secondTeamCurrentScore;
    this.countInterval = setInterval(() => {
      if (this.firstValueToReach > this.firstTeamCounterValue) {
        this.firstTeamCounterValue += 1;
      }
      if (this.secondValueToReach > this.secondTeamCounterValue) {
        this.secondTeamCounterValue += 1;
      }
    }, 50);
  },
  unmounted() {
    clearInterval(this.countInterval);
  },
  watch: {
    firstTeamCurrentScore: function (val) {
      this.firstValueToReach = val;
    },
    secondTeamCurrentScore: function (val) {
      this.secondValueToReach = val;
    },
  },
};
</script>
lepak
  • 369
  • 2
  • 9
  • This is unoptimized, because you still call setInterval after the counts have been reached – Nino Filiu Nov 13 '20 at 08:54
  • Also this will increment indefinitely when `firstTeamCurrentScore` gets updated to a value that is below `firstTeamCounterValue` – Nino Filiu Nov 13 '20 at 11:17