Image

animating-vue

6 min read
Last update: December 19, 2021

State with GSAP

It’s not uncommon for a web app to display data that is always changing. Whether this is the real-time score of players in an online game, or some sort of company or financial stats, it’s helpful to know how to display this kind of always-changing state in a nice way. In this lesson, we’ll look at using GSAP to achieve this.

Under the hood, GSAP performs “tweens” for us. But what does that actually mean? Tweening is short for in-betweening, which is a process of generating the frames that will be displayed in between the beginning and end of the animation. For example, the values be_tween_ 1 and 10 are: 2, 3, 4, 5, 6, 7, 8 and 9 (and their decimals, respectively: 2.123…). In the case of scaling from 0% to 100%, the values in be_tween_ would be everything greater than 0 and less than 100. So by using GSAP, we can tween between values naturally and performantly.

Let’s look at an example, where we have some always-changing state and display that state as a bar graph. This could be useful for something like a realtime scoreboard, or anywhere you’re displaying data that is constantly changing in real time.

📃 src/views/State.vue

<template>
    <div>
        <div :style="{ width: number + 'px' }" class="bar">
            <span>{{ number }}</span>
        </div>
    </div>
</template>

<script>
    export default {
        data() {
            return {
                number: 0
            }
        },
        methods: {
            randomNumber() {
                this.number = Math.floor(Math.random() * (800 - 0))
            }
        },
        created() {
            setInterval(this.randomNumber, 1500)
        }
    }
</script>

<style scoped>
    .bar {
        padding: 5px;
        background-color: #2c3e50;
        border: 1px #16c0b0 solid;
        min-width: 20px;
    }

    .bar span {
        color: white;
    }
</style>

To create some dummy data, when this component is created, we’re using setInterval to run our randomNumber method every 1500ms. The randomNumber method updates our number data with a new value (ranging from 0-800), and our template uses that always-changing number to set the width of our div via style binding: :style="{ width: number + 'px' }" We’re also displaying the number value itself in the span.

As-is, our bar width jumps from value to value as the number changes, and the number it displays simply updates to the new number. However, wouldn’t it be nice if our bar smoothly grew and shrank to its new width value? Simultaneously, the number value it displayed could tween as well. Let’s make this happen.

We’ll start out by importing GSAP.

📃 src/views/State.vue

<script>
    import gsap from 'gsap'
    ...
</script>

Now we can use it to generate the values between the old number and the new number. To do this, we’ll use watch. If you haven’t used this before, it’s a built-in Vue component option that allows us to watch a reactive value, and when that value changes, we can do things like perform operations using the new and/or old value. So let’s watch our number data value.

📃 src/views/State.vue

watch: {
number(newValue) {
// now what?
}
},

Notice how we simply created a watcher by the same name as the data value it is watching (number), and we’re passing it the newValue. Before we move on, let’s create another data value by the name of tweenedNumber. You’ll understand why in a moment.

📃 src/views/State.vue

data() {
return {
number: 0,
tweenedNumber: 0
}
},

Now that we have the number and tweenedNumber data values, we can use them in our watcher with the gsap.to() method. In this case, we don’t need GSAP to animate an element directly, we want it to animate our data, because it’s our data value that is being used to widen our bar, and we’re displaying that data value within the bar.

So instead of passing GSAP the element we want it to animate, we’re going to pass in the data we want it to animate:

📃 src/views/State.vue

watch: {
number(newValue) {
gsap.to(this.$data, {})
}
},

We’ve now passed in our component’s data object with this.$data. Now GSAP has access to our data, and can update its properties. Now we can give this animation an object including instructions about how to animate our data. We’ll give it a duration of 1 second, an ease, and specify the data value we want to animate.

📃 src/views/State.vue

watch: {
number(newValue) {
gsap.to(this.$data, {
duration: 1,
ease: 'circ.out'
tweenedNumber: newValue
})
}
},

Notice how we’re telling gsap to update the tweenedNumber property on our data (on this.$data) and animate it to the newValue. By the very nature of the gsap.to() method, it will animate that value by tweening it up or down to that new value, instead of just jumping abruptly to that value.

In other words, instead of jumping from 1 directly to 5, this method would create a path of smooth steps that arrive at 5 by going from 1 and progressively getting higher and closer until it reaches 5. Because we have these be_tween_ values, we can now use them to smooth out what’s happening in our template.

Instead of binding our bar style to number, let’s bind it to tweenedNumber, and display that number as well.

📃 src/views/State.vue

<div :style="{ width: tweenedNumber + 'px' }" class="bar">
    <span>{{ tweenedNumber }}</span>
</div>

Now, if we check this out in the browser, our width is now expanding and shrinking based on those incrementally tweened values. You’ll also notice that because we’re now displaying that tweenedNumber we’re seeing all these decimals.

While these are helpful for a smooth tween, they’re not so pretty to look at, so let’s just add .toFixed(0) to where we’re displaying the value in our span, to clean this up.

📃 src/views/State.vue

<div :style="{ width: tweenedNumber + 'px' }" class="bar">
    <span>{{ tweenedNumber.toFixed(0) }}</span>
</div>

Now we’ll be displaying only the whole number and not the decimal portion.

Here’s the final code for the component all together:

📃 src/views/State.vue

<template>
    <div>
        <div :style="{ width: tweenedNumber.toFixed(0) + 'px' }" class="bar">
            <span>{{ tweenedNumber.toFixed(0) }}</span>
        </div>
    </div>
</template>

<script>
    import {
        gsap
    } from 'gsap'

    export default {
        data() {
            return {
                number: 0,
                tweenedNumber: 0
            }
        },
        watch: {
            number(newValue) {
                gsap.to(this.$data, {
                    duration: 1,
                    ease: 'circ.out',
                    tweenedNumber: newValue
                })
            }
        },
        methods: {
            randomNumber() {
                this.number = Math.random() * (800 - 0)
            }
        },
        created() {
            setInterval(this.randomNumber, 1500)
        }
    }
</script>

<style scoped>
    .bar {
        padding: 5px;
        background-color: #2c3e50;
        border: 1px #16c0b0 solid;
        min-width: 20px;
    }

    .bar span {
        color: white;
    }
</style>

Let’s ReVue

In this lesson, we’ve learned about the concept of tweening, and how to use GSAP to create smooth transitions between changing state in our app. In the next lesson, we’ll learn how to use GSAP’s timelines to create a sequence containing multiple animations.