w3resource
Vue Tutorial

State Transitions

State Transitions

Vue's transition system offers many ways for us to animate entering, leaving, and lists, but what about animating our data itself? For instance:

  • Calculations and numbers
  • colors displayed
  • the positions of SVG nodes
  • the sizes and other properties of elements

All of these data are either already stored as raw numbers or they can be converted into numbers. When we have done that, we can then animate these state changes using 3rd-party libraries to tween states, in combination with Vue's component and reactivity systems.

Animating State with Watchers

A watcher allows us to animate changes of any numerical property into another property. An example is as illustrated:

<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/1.20.3/TweenMax.min.js"></script>
<div id="animated-number-demo">
  <input v-model.number="number" type="number" step="20">
  <p>{{ animatedNumber }}</p>
</div><p>

JS

new Vue({
  el: '#animated-number-demo',
  data: {
    number: 0,
    tweenedNumber: 0
  },
  computed: {
    animatedNumber: function() {
      return this.tweenedNumber.toFixed(0);
    }
  },
  watch: {
    number: function(newValue) {
      TweenLite.to(this.$data, 0.5, { tweenedNumber: newValue });
    }
  }
})

Whenever we update the number, the change will be animated below the input. For something that is not directly stored as a number, such as any valid CSS color for instance: we could accomplish this with Color.js and Tween.js.

HTML

<script src="https://cdn.jsdelivr.net/npm/[email protected]"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]"></script>

<div id="example-7">
  <input
    v-model="colorQuery"
    v-on:keyup.enter="updateColor"
    placeholder="Enter a color"
  >
  <button v-on:click="updateColor">Update</button>
  <p>Preview:</p>
  <span
    v-bind:style="{ backgroundColor: tweenedCSSColor }"
    class="example-7-color-preview"
  ></span>
  <p>{{ tweenedCSSColor }}</p>
</div>

JS

var Color = net.brehaut.Color
new Vue({
  el: '#example-7',
  data: {
    colorQuery: '',
    color: {
      red: 0,
      green: 0,
      blue: 0,
      alpha: 1
    },
    tweenedColor: {}
  },
  created: function () {
    this.tweenedColor = Object.assign({}, this.color)
  },
  watch: {
    color: function () {
      function animate () {
        if (TWEEN.update()) {
          requestAnimationFrame(animate)
        }
      }

      new TWEEN.Tween(this.tweenedColor)
        .to(this.color, 750)
        .start()

      animate()
    }
  },
  computed: {
    tweenedCSSColor: function () {
      return new Color({
        red: this.tweenedColor.red,
        green: this.tweenedColor.green,
        blue: this.tweenedColor.blue,
        alpha: this.tweenedColor.alpha
      }).toCSS()
    }
  },
  methods: {
    updateColor: function () {
      this.color = new Color(this.colorQuery).toRGB()
      this.colorQuery = ''
    }
  }
})

CSS

.example-7-color-preview {
  display: inline-block;
  width: 50px;
  height: 50px;
}

Dynamic State Transitions

Just as in Vue's transition components, the data backing state transitions could be updated in real time, which is useful for prototyping.

Organizing Transitions into Components

The complexity of a Vue instance or component can quickly increase when managing state transitions. Good News is that many animations can be extracted out into dedicated child components as shown below:

HTML

<script src="https://cdn.jsdelivr.net/npm/[email protected]"></script>

<div id="example-8">
  <input v-model.number="firstNumber" type="number" step="20"> +
  <input v-model.number="secondNumber" type="number" step="20"> =
  {{ result }}
  <p>
    <animated-integer v-bind:value="firstNumber"></animated-integer> +
    <animated-integer v-bind:value="secondNumber"></animated-integer> =
    <animated-integer v-bind:value="result"></animated-integer>
  </p>
</div>

JS

Vue.component('animated-integer', {
  template: '<span>{{ tweeningValue }}</span>',
  props: {
    value: {
      type: Number,
      required: true
    }
  },
  data: function () {
    return {
      tweeningValue: 0
    }
  },
  watch: {
    value: function (newValue, oldValue) {
      this.tween(oldValue, newValue)
    }
  },
  mounted: function () {
    this.tween(0, this.value)
  },
  methods: {
    tween: function (startValue, endValue) {
      var vm = this
      function animate () {
        if (TWEEN.update()) {
          requestAnimationFrame(animate)
        }
      }

      new TWEEN.Tween({ tweeningValue: startValue })
        .to({ tweeningValue: endValue }, 500)
        .onUpdate(function () {
          vm.tweeningValue = this.tweeningValue.toFixed(0)
        })
        .start()

      animate()
    }
  }
})

// All complexity has now been removed from the main Vue instance!
new Vue({
  el: '#example-8',
  data: {
    firstNumber: 20,
    secondNumber: 40
  },
  computed: {
    result: function () {
      return this.firstNumber + this.secondNumber
    }
  }
})

We can use any combination of transition strategies covered in this series within child components, as well as those that are offered by Vue's built-in transition system.

Bringing Designs to Life

To animate means to bring something to life. However, when icons, logos, and mascots are created by designers, they are created as images or static SVGs. Vue can help us make our welcome page, notifications and loading indicators be more emotionally compelling, by transitioning between states in things like SVGs which are just data.