Website menu animation using GSAP

Let’s consider creating a simple menu animation using GSAP.
That’s the result we’ll get at the end.

Let’s start implementing website menu.
1.HTML
Our structure will be as follows:
<div class="wrapper">
<div class="base">
<button class="menu_trigger btnOpenMenu">Menu</button> // Menu opening button
<h1 class="title">Maketodoro</h1>
<nav class="menu mainMenu">
<div class="menu_w_decor"> // The wrapping block where the decors are stored which will be animated http://prntscr.com/q8nlwm
<div class="menu_decor_w_line">
<span class="menu_decor_line"></span>
</div>
<div class="menu_decor_w_line lg_mod">
<span class="menu_decor_line"></span>
</div>
<div class="menu_decor_w_line lg_mod">
<span class="menu_decor_line"></span>
</div>
<div class="menu_decor_w_line lg_mod">
<span class="menu_decor_line"></span>
</div>
<div class="menu_decor_w_line">
<span class="menu_decor_line"></span>
</div>
</div>
<button class="menu_trigger close_menu_mod btnCloseMenu">Close</button> // Menu close button
<ul class="menu_list"> // Menu items
<li class="menu_list_item">
<a href="#" class="menu_list_link">Home</a>
</li>
<li class="menu_list_item">
<a href="#" class="menu_list_link">About</a>
</li>
<li class="menu_list_item">
<a href="#" class="menu_list_link">Hover Mee</a>
</li>
<li class="menu_list_item">
<a href="#" class="menu_list_link">Contact</a>
</li>
</ul>
</nav>
</div>
</div>
2.SCSS
// Mixin with properties for better animation performance.
@mixin d3() {
will-change: transform;
transform: translateZ(0);
}
html {
font: 400 10px/1.33 'Montserrat';
}
body {
background-image: linear-gradient(135deg, #8f24ed 30%, #107cb3 100%);
}
.wrapper {
display: flex;
flex-direction: column;
height: 100vh;
overflow: hidden;
}
.base {
position: relative;
display: flex;
flex-direction: row;
max-width: 37.5em;
width: 100%;
height: 66.7em;
margin: auto;
padding: 2em;
font-size: 1rem;
border-radius: 1em;
box-shadow: 0 0 0 1.2em rgba(0, 0, 0, 0.1), 0 0 1px 1px rgba(0, 0, 0, 0.1), inset 0 0 0 1.2em white;
background-color: #eaecef;
overflow: hidden;
@media screen and (max-width: 374px) {
font-size: .8rem;
}
}
// Menu opening button
.menu_trigger {
position: absolute;
top: (2.4em/1.6);
right: (2.6em/1.6);
z-index: 1;
font-size: 1.6em;
font-weight: 900;
letter-spacing: -1px;
text-transform: uppercase;
cursor: pointer;
will-change: transform;
// The closing button initially has such a modifier, it is not visible + there is an offset to the left by 1em
&.close_menu_mod {
opacity: 0;
pointer-events: none;
transform: translateX(-1em);
}
// Styles for the cap which is under the button http://prntscr.com/q8noc3
&:before {
position: absolute;
bottom: -2px;
left: 0;
z-index: -1;
width: 100%;
height: (1.2em/1.6);
background-image: linear-gradient(0deg, #ee43bd 0%, #ffd779 100%);
transform: translateX(-.7rem);
will-change: transform;
content: "";
transition: all 0.2s ease;
}
// At hover we shift the plate that we have hanging at before to zero
&:hover,
&:focus {
&:before {
transform: translateX(0);
}
}
}
.title {
margin: auto;
max-width: 100%;
font-weight: 900;
font-size: 4em;
text-align: center;
line-height: (2.8em/3.2);
text-transform: uppercase;
}
// The menu will hang in absolute and stretch to the full width and height of the parent (block .base). Initially it will have opacity: 0 and pointer-events: none. We will change these properties later using gsap.
.menu {
position: absolute;
top: 0;
left: 0;
z-index: 1;
display: flex;
flex-direction: column;
width: 100%;
height: 100%;
padding: 2em;
opacity: 0;
pointer-events: none;
}
// Center the menu items
.menu_list {
margin: auto;
width: 100%;
}
.menu_list_item {
margin-bottom: .8em;
}
.menu_list_link {
display: block;
font-size: 5em;
color: #282828;
font-weight: 900;
text-decoration: none;
text-transform: uppercase;
letter-spacing: -1.2px;
transition: color .3s ease;
@include d3();
&:hover,
&:focus {
color: #8e24ec;
}
}
// As mentioned above, .menu_w_decor is a wrapper for all decors and is stretched to the full width and height of the menu.
.menu_w_decor {
position: absolute;
top: 0;
left: 0;
display: flex;
flex-direction: row;
width: 100%;
height: 100%;
border-radius: 1em;
overflow: hidden;
}
// .menu_decor_w_line is a wrapper for the menu_decor_line block, the width of the first and the last element is 2.1em. For all the others we calculate the width with calc(). this way we get this picture http://prntscr.com/q8nr2o. This block also has overflow: hidden, because we need the internal content of this block to stay within the size of the parent block when offset in the x axis.
.menu_decor_w_line {
height: 100%;
position: relative;
overflow: hidden;
&.lg_mod {
width: calc(33.33% - 1.4em);
}
&:first-child {
width: 2.1em;
}
&:last-child {
width: 2.1em;
}
}
// Here are the styles directly for the strips themselves. The stripes end up looking like this http://prntscr.com/q9opno. I specifically removed all the other stripes to make it clearer.
.menu_decor_line {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
opacity: 0;
background-color: #fff;
@include d3();
&:before {
position: absolute;
top: 0;
left: 0;
width: 1px;
height: 100%;
background-color: #edeff5;
content: "";
}
}
3.JavaScript
Let’s declare the variables.
let $btnOpenMenu,
$mainMenu,
$mainMenuItem,
$btnCloseMenu,
$menuDecorLine,
menuTl;
At onReady we assign values to them.
$(document).ready(function ($) {
$btnOpenMenu = $('.btnOpenMenu');
$btnCloseMenu = $('.btnCloseMenu');
$mainMenu = $('.mainMenu');
$mainMenuTitle = $('.menu_list_link');
$menuDecorLine = $('.menu_decor_line');
menuTl = gsap.timeline({paused: true});
});
Create a function with animation and time line.
const menuAnimFunc = () => {
menuTl
};
We show the menu.
.set($mainMenu, {
opacity: 1,
pointerEvents: "auto"
})
We set the initial rank for the bands -100%. Thus, they will not be visible from the start, because the parent has an overflow: hidden, so we get the effect of curtains.
.fromTo($menuDecorLine, 0.25, {
xPercent: -100,
}, {
xPercent: 0,
opacity: 1,
ease: Power1.easeOut
})
Animate menu items one by one using staggerFromTo.
.staggerFromTo($mainMenuTitle, .3, {
x: -50,
opacity: 0,
ease: Back.easeOut.config(1)
}, {
x: 0,
opacity: 1
}, .2)
Animate the appearance of the close button.
.to($btnCloseMenu, .1, {
opacity: 1,
pointerEvents: "auto",
translateX: 0
})
Add onClick event to the open menu button. We make a check if menuTl.isActive() returns false, i.e. if the animation does not work at the moment, then we start it.
$btnOpenMenu.on('click', () => {
if(!menuTl.isActive()) {
menuTl.play();
}
});
Exactly the same event we hang up on the menu close button. Only here we make menuTl.reverse(); instead of menuTl.play();
$btnCloseMenu.on('click', () => {
if(!menuTl.isActive()) {
menuTl.reverse();
}
});
At onLoad we start the function with animation.
$(window).on('load', function () {
menuAnimFunc();
});
You can see the final result by clicking on the link. That’s all, thank you for your attention.