In today's tutorial, we'll learn how to create a JavaScript page loading animation with GSAP, one of the most dominant and popular JavaScript animation libraries available.
To better understand what we’re going to build, check out the demo page. Be sure to click on the menu links to repeat the animation.
These kinds of animations will work really well in combination with page transition libraries like barba.js and Highway.js.
Page Animation Demo
For this tutorial our demo won't live on CodePen. As we need different pages to showcase the animation, I decided that it's better to host it on GitHub. Here's the project structure:
panels-animation/ ├── about.html ├── contact.html ├── index.html ├── main.css └── main.js
Before we continue, it's worth noting that the inspiration for this demo is taken from Pure Cinema's website.
1. Begin With the Page Markup
Let's describe the markup for the index.html
page. This will be similar to the other pages.
Inside it, we'll place:
- A typical page header.
- The panels that will be responsible for splitting the screen into six equal parts.
- The
main
element where the page's main content will live.
Additionally, we'll import:
- For stylistic reasons, the Montserrat Google Font
- GSAP's latest version
- Our own CSS and JavaScript files
With all the above in mind, here's the associated markup:
<!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="preconnect" href="https://fonts.gstatic.com"> <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;700&display=swap"> <link rel="stylesheet" href="main.css"> <title>Simple Page Loading Animation With GSAP</title> </head> <body> <header class="page-header"> <nav> <ul> <li> <a href="index.html">Home</a> </li> <!-- more list items --> </ul> </nav> </header> <ul class="panels"> <li class="panel"></li> <li class="panel"></li> <li class="panel"></li> <li class="panel"></li> <li class="panel"></li> <li class="panel"></li> </ul> <main class="page-main"> <div> <h1>Home Page</h1> <!-- put more content here --> </div> </main> <script src="https://unpkg.co/gsap@3/dist/gsap.min.js"></script> <script src="main.js"></script> </body> </html>
2. Define Some Basic Styles
As usually, we'll continue with some CSS variables and reset styles:
:root { --panel-width: calc(100% / 6); --darkblue: #02020c; --white: #fff; --lightgray: #fafafb; } * { padding: 0; margin: 0; box-sizing: border-box; } ul { list-style: none; } a { color: inherit; text-decoration: none; } h1 { font-size: 3rem; } body { height: 100vh; font-family: "Montserrat", sans-serif; color: var(--white); overflow: hidden; }
Two things to note:
- The
panel-width
variable will determine the panel width. - The page height will be equal to the viewport height.
3. Specify the Main Styles
Let's now concentrate on the main styles. We'll leave out the header styles as they haven't any importance.
Panel Container
The panel container will be a fixed positioned and fullscreen element. It will be horizontally centered and skewed to a certain degree. Besides, due to its distortion, its width will always exceed and depend on the viewport width. As we're going to animate its clip-path
property later, we'll define a default value for this property that will denote the full visibility of the element.
The Panels
The panels will be absolutely positioned elements, and their width
and left
property values will depend on the panel-width
variable. That said, the left
value for the first panel will be 0, for the second one around 16.666%, for the third one around 33.333%, and so on. Plus, initially, they will be invisible. When the page loads, they will appear with a slide-in animation either from top to bottom or from bottom to top.
Main Element
The main
element will be fullscreen with horizontally and vertically centered content. In this case, it will contain only a heading, but if you use content whose height exceeds the viewport height, a scrollbar will appear. Initially, only 20% of it will be visible. When the page loads, the whole element will appear.
Here are the relevant styles:
.panels { position: fixed; top: 0; left: 50%; width: 180vw; height: 100%; transform: translateX(-50%) skewX(-35deg); clip-path: circle(100%); z-index: 1; background: var(--lightgray); } .panels .panel { position: absolute; top: 0; left: 0; bottom: 0; width: var(--panel-width); transform: scaleY(0); transform-origin: top; background: var(--darkblue); } .panels .panel:nth-child(even) { transform-origin: bottom; } .panels .panel:nth-child(2) { left: calc(var(--panel-width) - 1px); } .panels .panel:nth-child(3) { left: calc(calc(var(--panel-width) * 2) - 2px); } .panels .panel:nth-child(4) { left: calc(calc(var(--panel-width) * 3) - 4px); } .panels .panel:nth-child(5) { left: calc(calc(var(--panel-width) * 4) - 5px); } .panels .panel:nth-child(6) { left: calc(calc(var(--panel-width) * 5) - 6px); } .page-main { display: flex; height: 100%; padding: 100px 15px; clip-path: circle(20%); overflow-y: auto; background: var(--darkblue); } .page-main > div { text-align: center; margin: auto; } @media (max-width: 1024px) { .panels { width: 200vw; } } @media screen and (max-width: 600px) { .panels { width: 235vw; } }
Note: if you check the left
value of the panels, you'll notice there's an outer calc()
function. Its job is to make the panels overlap a little bit, and thus prevent the white borders between the adjacent panels. The subtrahend values (e.g. -2px) came from trial and error.
4. Fire the Animations
Let's now put GSAP on the game.
To create the animations, we'll take advantage of Timeline, an animation tool that will give us the ability to create a sequence of tweens/animations.
So first, we'll create a Timeline and set its state as paused. In this way, the animations inside it won't play by default. When the page has fully loaded, they will play thanks to its play()
method.
Inside it, we'll add a sequence of tweens by using its to()
method. This method can receive the following parameters:
- The DOM element that we want to animate.
- An object that will contain the properties that should be animated along with their respective end values. This object can receive additional properties like the
duration
one that will control the animation duration in seconds. - The placement of the tween in the timeline. In other words, when this tween should run. If we don't specify a value for it, it will be added to the end of the timeline.
In our case, the animation chain will be as follows :
- The first and last panels will appear. The animation duration will be 1 second.
- The other panels will start appearing 0.5 seconds before the end of the timeline. The animation duration will be 0.5 seconds. Note that we omit the
duration
property as its default value is0.5
. - All the panels will start disappearing with a 0.05 seconds delay between each other. That said, the first panel will start, then the second one after 0.05 seconds delay will continue, then the third one after 0.1 seconds delay, and so on. The animation duration will be 0.3 seconds.
- The panel container will disappear via the
clip-path
property. The animation duration will be 1 second. - Finally, the page's
main
element will start growing via theclip-path
property 0.3 seconds before the end of the timeline. The animation duration will also be 1 second.
Let me explain one tricky thing about the first two tweens.
The slide-in animation of all panels will finish at the same time. Remember that it will last 1 second for the first and last panels, while 0.5 seconds for the other ones, yet in this case, it will start 0.5 seconds earlier. This behavior isn't clear enough in our demo because the styles hide a part of the first and last panels. But, if we remove the skew from the panel container and give it width: 100vw
, we'll be able to test it. Consider this CodePen demo.
Here's the associated JavaScript code:
const tl = gsap.timeline({ paused: true }); tl.to(".panels .panel:first-child, .panels .panel:last-child", { scaleY: 1, duration: 1 }) .to( ".panels .panel:not(:first-child):not(:last-child)", { scaleY: 1 }, "-=0.5" ) .to(".panels .panel", { scaleY: 0, duration: 0.3, stagger: 0.05 }) .to(".panels", { clipPath: "circle(0%)", skewX: 0, duration: 1 }) .to( ".page-main", { clipPath: "circle(100%)", duration: 1 }, "-=0.3" ); window.addEventListener("load", function () { tl.play(); });
Of course, you can use the browser console to check the footprint left by the plugin.
Bonus
We can use the Timeline's duration()
method to retrieve its duration. In our case, this will be 3.25 seconds. Let's understand the origin of this number:
- 1 second from the first tween
- 0 seconds from the second one as it will finish at the same time as the first one.
- 0.55 seconds from the third one as the last panel will start disappearing with 0.25 seconds delay (0.05 seconds x 5). So, the total time will come from the addition of the delay and the animation duration (i.e. 0.25 + 0.3).
- 1 second from the fourth one
- 0.7 seconds from the last one as it will start 0.3 seconds before the end of the timeline.
Conclusion
Congrats, folks! We managed to build an attractive page loading animation by taking advantage of the GSAP animation library. This project is nothing more than a very small sample of the GSAP capabilities. Feel free to extend it according to your needs (e.g. make the tweens faster), thus expand your GSAP knowledge. Even better, try to incorporate it into a page transition library like barba.js.
If you want more GSAP tutorials or tutorials related to other animation libraries, let us know via social media.
As always, thanks a lot for reading!
No comments:
Post a Comment