A Journey into Animated Loaders with GSAP - Part 2
It's been a minute! Join me once more on a GSAP tutorial so epic it took a year to write.
Dang…is it November already?! Time is a funny thing when you’re having this much fun.
Welcome back grasshopper, what a crazy year these last 12 months have been. I hope you have made it here unscathed. We will need your good vibes for what lies ahead. Brace yourself for one more ride down the GSAP roller-coaster, this post is going to be rad!
Let’s begin
In the first part of this beautiful duet, I showed you how to create a simple loader using GSAP; we turned a circle into a square and vice-versa.
For this second part, we’re taking our inspiration directly from the 90s. Specifically, from a classic and revolutionary movie you Americans know as Space Jam.
Now go find your old Air Jordans because you’re going to learn how to create a bouncy basketball!
Structuring the HTML
The animation we’re working with is made of two elements: one for the ball and one for the shadow.
<div class="ball"></div>
<div class="shadow"></div>
Adding the CSS
Before we start, I like to reset all the properties and center the elements in the screen using flexbox:
* {
padding: 0;
margin: 0;
box-sizing: border-box;
}
body {
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
height: 100vh;
background-color: #ffc30f;
}
OK, now we can style this beauty:
.ball {
width: 60px;
height: 60px;
border-radius: 50%;
background-color: #c70039;
background: linear-gradient(to bottom, rgba(199,0,56,1) 0%, rgba(144,12,63,1) 100%);
transform: translateY(-100px);
}
.shadow {
width: 60px;
height: 30px;
background: radial-gradient(ellipse at center, rgba(88,24,69,1) 0%, rgba(237,237,237,0) 50%);
position: relative;
z-index: -1;
transform: translateY(75px);
opacity: .1;
}
Notice that:
- We added
position: relative
andz-index: 1
. This way we ensure that the shadow will always remain behind the ball. - We moved the ball a fraction upward and the shadow a fraction downwards. This will be the initial state of the animation.
Animation states
Let’s review what we want to do. I created this advanced chart to demonstrate:
- In the first state, the ball starts at its highest position and we dim the shadow, like some sort of invisible floor.
- In the second state, the ball reaches its lowest position, i.e., the “floor.”
- In the final state, we smash the ball slightly to simulate its impact on the surface. We also increase the size and strength of the shadow to simulate its proximity.
Understanding the timeline
As I mentioned in the previous article, GSAP is made of 4 libraries that allow us to create isolated or chained animations.
For the isolated animations, we create tweens using the TweenLite or TweenMax libraries.
For the chained animations, we use the TimelineLite or TimelineMax libraries. These help us create a tween sequence in chronological order.
The main advantage of a Timeline is that the animation can be controlled with methods like play()
, stop()
, restart()
and reverse()
, giving us total control over the timing of events.
In our animation, we’ll use the TimelineMax library. It has all the TimelineLite functionality, plus other important properties like yoyo
and repeat
that we’ll use ahead.
The ball animation
Let’s start by selecting the elements:
var ball = document.querySelector('.ball');
var shadow = document.querySelector('.shadow');
Then we initiate the Timeline:
var tl = new TimelineMax();
And add the first animation, moving the ball for a half second downwards with the method to()
:
var tl = new TimelineMax();
tl.to(ball, .5, {
y: -100
});
Next, we want to make the ball reach the “floor” quickly.
In order to do that in a fraction of second we’ll reduce the ball’s height and change its interior border-roundness slightly creating that impact effect:
var tl = new TimelineMax();
tl.to(ball, .5, {
y: 100
})
.to(ball, .10, {
scaleY: 0.6,
transformOrigin: 'center bottom',
borderBottomLeftRadius: '40%',
borderBottomRightRadius: '40%'
});
Note: The property transformOrigin: "center bottom"
is added to make the impact animation more realistic. By default, the scale animation always occurs towards the center. By changing it to “center bottom” the animation occurs towards the center, over the X axis and towards the bottom over the Y axis.
Shadow animation
We’ve already done all the animations related to the ball, now let’s start the shadow animation.
We want the shadow to get bigger and stronger as the ball gets closer to the imaginary floor. Let’s do this by adding one more to()
method into the timeline.
var tl = new TimelineMax();
tl.to(ball, .50, {
y: 100
})
.to(ball, .10, {
scaleY: 0.6,
transformOrigin: 'center bottom',
borderBottomLeftRadius: '40%',
borderBottomRightRadius: '40%'
})
.to(shadow, .5, {
width: 90,
opacity: .7
});
In the example above, the animations occur after the ball animation. As the ball overlaps the shadow, we can’t see it.
We want the shadow animation to occur at the same time. One way of doing that is using labels.
Labels
A label is a point in the timeline that we can use as a reference.
To make it clearer, let’s add a label named “start” in the beginning of the Timeline and refer to it in the shadow animation.
var tl = new TimelineMax();
tl.add('start')
.to(ball, .50, {
y: 100
})
.to(ball, .10, {
scaleY: 0.6,
transformOrigin: 'center bottom',
borderBottomLeftRadius: '40%',
borderBottomRightRadius: '40%'
})
.to(shadow, .50, {
width: 90,
opacity: .7
}, 'start');
What we’re doing is telling the shadow animation to start at the point marked in the timeline as “start”.
This way, the shadow animation is as long as the ball animation. Both begin and end at the same time.
Repeating it infinitely
We have everything we need to make the ball bounce, now all we need to do is refine it.
First, we make the animation repeat infinitely. To do that we pass the property repeat: -1
to the Timeline instance.
var tl = new TimelineMax({ repeat: -1 });
However, this alone isn’t enough. The animation doesn’t work on a continuous flow. Instead, it’s always restarting from its initial state, doing a kind of “jump.”
To make the animation return to its initial state without the “jump,” we also pass the property yoyo: true
to the Timeline instance:
var tl = new TimelineMax({ repeat: -1, yoyo: true });
Easing
The last step is adding fluidity to the animation.
That means applying an easing function. This way the animation doesn’t seem “hard” and unnatural.
GSAP provides a lot of pre-defined ease functions, you can see them here.
In this tutorial, we want the ball to look like it’s being affected by the gravity, i.e., it speeds up when going down and slows down when going up.
To add that effect, let’s use an ease function called Circ.easeIn.
Here we add that function to the ball and shadow animations:
var tl = new TimelineMax({ repeat: -1, yoyo: true });
tl.add('start')
.to(ball, .50, {
y: 100,
ease: Circ.easeIn
})
.to(ball, .10, {
scaleY: 0.6,
transformOrigin: 'center bottom',
borderBottomLeftRadius: '40%',
borderBottomRightRadius: '40%'
})
.to(shadow, .50, {
width: 90,
opacity: .7,
ease: Circ.easeIn
}, 'start');
And voilà!
In case it looks too much like the ball is sticking on the ground before bouncing, we can advance the “smash” animation.
To create a better effect, we can add a value after its animation properties. For this example, the value will be "-=0.075"
. This way, we indicate that the animation properties must be applied earlier in the timeline, i.e., while the previous animation is still running!
Now the final result feels smooth and natural.
The loader is finally done.
var ball = document.querySelector('.ball');
var shadow = document.querySelector('.shadow');
var tl = new TimelineMax({ repeat: -1, yoyo: true });
tl.add('start')
.to(ball, .50, {
y: 100,
ease: Circ.easeIn
})
.to(ball, .10, {
scaleY: 0.6,
transformOrigin: 'center bottom',
borderBottomLeftRadius: '40%',
borderBottomRightRadius: '40%'
}, '-=0.075')
.to(shadow, .50, {
width: 90,
opacity: .7,
ease: Circ.easeIn
}, 'start');
Conclusion
There are infinite ways to create a bouncy ball, some simpler, others more complex. This post has the humble intention of showing you how a GSAP timeline works in a practical way.
For more examples of Timeline animations, check out this link.
In the last episode of this trilogy, we’ll explore how to create staggered animations.
I look forward to our next encounter. Stay safe out there friends!