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.