New Check out the curated list of the finest design resources I've gathered for you! Take a look →

How to Create a Sticky Navigation using Tailwind and GSAP

In this case study, I will share my experience on how to create a sticky navigation using Tailwind CSS and GSAP by cloning the header components from the Yama Vans website.

Yama Vans Website - Sticky Navigation
Yama Vans Website – Sticky Navigation

Create a Sticky Navigation using Tailwind and GSAP: Desktop Version

HTML and CSS

During the development process, I utilized Tailwind CSS to create the components. Here is the code for the desktop version:

<header class="header-yama bg-white z-[1000] sticky top-0">
	<div class="pre-center">
		<div class="center flex justify-start items-center pt-[2.625rem] pb-[2.5rem] max-w-[85rem]">
			<a href="index.html" class="header-yama__logo inline-flex justify-center items-center h-[3.5rem]">
				<img src="./images/logo.png" alt="Rizwan Aritonang" class="w-full h-full">
			</a>

			<div class="header-yama__nav flex-1">
				<div class="header-yama__nav-inner flex justify-center items-center">
					<a href="#" class="header-yama__link inline-flex mb-[0.3rem] relative text-[#1a1a1a] no-underline text-[0.9375rem] uppercase font-semibold mx-[0.9375rem] group">
						<span class="header-yama__link-text block relative tracking-[0.0938rem]">Process</span>
						<span class="header-yama__link-line absolute w-full bg-[#1a1a1a] scale-x-0 block h-[0.125rem] bottom-0 left-0 group-hover:scale-x-100" style="transform-origin: top left; transition: transform 0.3s ease;"></span>
					</a>
					<a href="#" class="header-yama__link inline-flex mb-[0.3rem] relative text-[#1a1a1a] no-underline text-[0.9375rem] uppercase font-semibold mx-[0.9375rem] group">
						<span class="header-yama__link-text block relative tracking-[0.0938rem]">Build Spotlight</span>
						<span class="header-yama__link-line absolute w-full bg-[#1a1a1a] scale-x-0 block h-[0.125rem] bottom-0 left-0 group-hover:scale-x-100" style="transform-origin: top left; transition: transform 0.3s ease;"></span>
					</a>
					<a href="#" class="header-yama__link inline-flex mb-[0.3rem] relative text-[#1a1a1a] no-underline text-[0.9375rem] uppercase font-semibold mx-[0.9375rem] group">
						<span class="header-yama__link-text block relative tracking-[0.0938rem]">About</span>
						<span class="header-yama__link-line absolute w-full bg-[#1a1a1a] scale-x-0 block h-[0.125rem] bottom-0 left-0 group-hover:scale-x-100" style="transform-origin: top left; transition: transform 0.3s ease;"></span>
					</a>
					<a href="#" class="header-yama__link inline-flex mb-[0.3rem] relative text-[#1a1a1a] no-underline text-[0.9375rem] uppercase font-semibold mx-[0.9375rem] group">
						<span class="header-yama__link-text block relative tracking-[0.0938rem]">Stories</span>
						<span class="header-yama__link-line absolute w-full bg-[#1a1a1a] scale-x-0 block h-[0.125rem] bottom-0 left-0 group-hover:scale-x-100" style="transform-origin: top left; transition: transform 0.3s ease;"></span>
					</a>
				</div>
			</div>

			<a href="#" class="header-yama__button text-white uppercase bg-[#242424] py-[0.625rem] px-[1.25rem]  text-[0.875rem] font-semibold tracking-[0.0938rem] inline-flex border-[2px] border-solid border-[#242424] transition-colors hover:bg-transparent hover:text-[#242424]">Start Your Build</a>
		</div>
	</div>
</header>

Javascript

Firstly, let’s create two files:

  1. header-yama_index.js
  2. header-yama_item.js

Inside header-yama_item.js, add the following code:

import { gsap } from "gsap";
import { ScrollTrigger } from "gsap/ScrollTrigger";
gsap.registerPlugin(ScrollTrigger);

export class Item {
	// DOM elements
	DOM = {
		// Main element (.header-yama)
		el: null,
	}

    /**
     * Constructor.
     * @param {Element} DOM_el - the .menu element
     */
	constructor(DOM_el) {
		this.DOM = {el: DOM_el};

		this.initEvents();
	}

    /**
     * Initializes some events.
     */
	initEvents(){
		this.animateOnScroll();
	}

    /**
     * ScrollTrigger animations for scrolling
     */
	animateOnScroll() {
		const showAnim = gsap.from(this.DOM.el, {
			yPercent: -100,
			paused: true,
			duration: 0.4
		})
		.progress(1)

		ScrollTrigger.create({
			start: "top top",
			end: 99999,
			onUpdate: (self) => {
			  self.direction === -1 ? showAnim.play() : showAnim.reverse()
			}
		});
	}
}

Inside header-yama_index.js, add the following code:

import { Item } from './header-yama_item'

new Item(document.querySelector('.header-yama'));

Now, let’s include header-yama_index.js in the HTML file. Add this code before the </body> closing tag:

<script src="./js/header-yama_index.js" type="module"></script>

Create a Sticky Navigation using Tailwind and GSAP: Mobile Version

HTML and CSS

Let’s update the HTML using Tailwind CSS as shown below:

<header class="header-yama bg-white z-[1000] sticky top-0">
	<div class="pre-center">
		<div class="center flex justify-start items-center pt-[2.625rem] pb-[2.5rem] max-w-[85rem] tablet:justify-between tablet:py-[2rem] mobile-landscape:py-[1rem]">
			<a href="index.html" class="header-yama__logo inline-flex justify-center items-center h-[3.5rem] z-[600]">
				<img src="./images/logo.png" alt="Rizwan Aritonang" class="w-full h-full">
			</a>

			<div class="header-yama__nav flex-1 tablet:hidden">
				<div class="header-yama__nav-inner flex justify-center items-center">
					<a href="#" class="header-yama__link inline-flex mb-[0.3rem] relative text-[#1a1a1a] no-underline text-[0.9375rem] uppercase font-semibold mx-[0.9375rem] group">
						<span class="header-yama__link-text block relative tracking-[0.0938rem]">Process</span>
						<span class="header-yama__link-line absolute w-full bg-[#1a1a1a] scale-x-0 block h-[0.125rem] bottom-0 left-0 group-hover:scale-x-100" style="transform-origin: top left; transition: transform 0.3s ease;"></span>
					</a>
					<a href="#" class="header-yama__link inline-flex mb-[0.3rem] relative text-[#1a1a1a] no-underline text-[0.9375rem] uppercase font-semibold mx-[0.9375rem] group">
						<span class="header-yama__link-text block relative tracking-[0.0938rem]">Build Spotlight</span>
						<span class="header-yama__link-line absolute w-full bg-[#1a1a1a] scale-x-0 block h-[0.125rem] bottom-0 left-0 group-hover:scale-x-100" style="transform-origin: top left; transition: transform 0.3s ease;"></span>
					</a>
					<a href="#" class="header-yama__link inline-flex mb-[0.3rem] relative text-[#1a1a1a] no-underline text-[0.9375rem] uppercase font-semibold mx-[0.9375rem] group">
						<span class="header-yama__link-text block relative tracking-[0.0938rem]">About</span>
						<span class="header-yama__link-line absolute w-full bg-[#1a1a1a] scale-x-0 block h-[0.125rem] bottom-0 left-0 group-hover:scale-x-100" style="transform-origin: top left; transition: transform 0.3s ease;"></span>
					</a>
					<a href="#" class="header-yama__link inline-flex mb-[0.3rem] relative text-[#1a1a1a] no-underline text-[0.9375rem] uppercase font-semibold mx-[0.9375rem] group">
						<span class="header-yama__link-text block relative tracking-[0.0938rem]">Stories</span>
						<span class="header-yama__link-line absolute w-full bg-[#1a1a1a] scale-x-0 block h-[0.125rem] bottom-0 left-0 group-hover:scale-x-100" style="transform-origin: top left; transition: transform 0.3s ease;"></span>
					</a>
				</div>
			</div>

			<div class="header-yama__nav-mobile hidden bg-white tablet:z-[500] fixed w-full top-0 right-0 bottom-0 left-0 h-0">
				<div class="header-yama__nav-mobile-inner h-[120%] pt-[20vh] relative flex flex-col items-start justify-start px-[5%] pb-[8vh] opacity-0 w-full">
					<a href="#" class="header-yama__link mb-[0.75rem] relative text-[#1a1a1a] w-full no-underline text-[2.5rem] font-serif block group ">
						<span class="header-yama__link-text block relative tracking-[0.125rem]">Process</span>
						<span class="header-yama__link-line absolute w-full bg-[#1a1a1a] scale-x-0 block h-[0.125rem] bottom-0 left-0 group-hover:scale-x-100" style="transform-origin: top left; transition: transform 0.3s ease;"></span>
					</a>

					<a href="#" class="header-yama__link mb-[0.75rem] relative text-[#1a1a1a] w-full no-underline text-[2.5rem] font-serif block group ">
						<span class="header-yama__link-text block relative tracking-[0.125rem]">Build Spotlight</span>
						<span class="header-yama__link-line absolute w-full bg-[#1a1a1a] scale-x-0 block h-[0.125rem] bottom-0 left-0 group-hover:scale-x-100" style="transform-origin: top left; transition: transform 0.3s ease;"></span>
					</a>

					<a href="#" class="header-yama__link mb-[0.75rem] relative text-[#1a1a1a] w-full no-underline text-[2.5rem] font-serif block group ">
						<span class="header-yama__link-text block relative tracking-[0.125rem]">About</span>
						<span class="header-yama__link-line absolute w-full bg-[#1a1a1a] scale-x-0 block h-[0.125rem] bottom-0 left-0 group-hover:scale-x-100" style="transform-origin: top left; transition: transform 0.3s ease;"></span>
					</a>

					<a href="#" class="header-yama__link mb-[0.75rem] relative text-[#1a1a1a] w-full no-underline text-[2.5rem] font-serif block group ">
						<span class="header-yama__link-text block relative tracking-[0.125rem]">Stories</span>
						<span class="header-yama__link-line absolute w-full bg-[#1a1a1a] scale-x-0 block h-[0.125rem] bottom-0 left-0 group-hover:scale-x-100" style="transform-origin: top left; transition: transform 0.3s ease;"></span>
					</a>

					<a href="#" class="header-yama__link mb-[0.75rem] relative text-[#1a1a1a] w-full no-underline text-[2.5rem] font-serif block group ">
						<span class="header-yama__link-text block relative tracking-[0.125rem]">Contact</span>
						<span class="header-yama__link-line absolute w-full bg-[#1a1a1a] scale-x-0 block h-[0.125rem] bottom-0 left-0 group-hover:scale-x-100" style="transform-origin: top left; transition: transform 0.3s ease;"></span>
					</a>
				</div>
			</div>

			<a href="#" class="header-yama__button text-white uppercase bg-[#242424] py-[0.625rem] px-[1.25rem]  text-[0.875rem] font-semibold tracking-[0.0938rem] inline-flex border-[2px] border-solid border-[#242424] transition-colors hover:bg-transparent hover:text-[#242424] tablet:hidden">Start Your Build</a>

			<div class="header-yama__hamburger z-[600] relative w-[3.75rem] cursor-pointer h-[2.25rem] p-[10px] items-center justify-center flex-col gap-[0.3125rem] hidden tablet:flex ">
				<div class="header-yama__hamburger-top w-full h-[0.125rem] bg-[#242424]"></div>
				<div class="header-yama__hamburger-middle  w-full h-[0.125rem] bg-[#242424]"></div>
				<div class="header-yama__hamburger-bottom  w-full h-[0.125rem] bg-[#242424]"></div>
			</div>
		</div>
	</div>
</header>

JavaScript

Update the JavaScript code as follows:

import { gsap } from "gsap";
import { ScrollTrigger } from "gsap/ScrollTrigger";
gsap.registerPlugin(ScrollTrigger);

export class Item {
	// DOM elements
	DOM = {
		// Main element (.header-yama)
		el: null,
		// Hamburger Menu
		hamburgerCtrl: {
			el: null,
			lineTop: null,
			lineMiddle: null,
			lineBottom: null
		},
		// rwdNav Menu
		rwdMenu: {
			el: null,
			inner: null
		}
	}

	// Checks if the responsive menu is open or currently animating
	headerStatus = {
		isOpen: false,
	}

    /**
     * Constructor.
     * @param {Element} DOM_el - the .menu element
     */
	constructor(DOM_el) {
		this.DOM = {el: DOM_el};
		this.DOM.hamburgerCtrl = {el: this.DOM.el.querySelector('.header-yama__hamburger')};
		this.DOM.hamburgerCtrl.lineTop = this.DOM.el.querySelector('.header-yama__hamburger-top');
		this.DOM.hamburgerCtrl.lineMiddle = this.DOM.el.querySelector('.header-yama__hamburger-middle');
		this.DOM.hamburgerCtrl.lineBottom = this.DOM.el.querySelector('.header-yama__hamburger-bottom');
		this.DOM.rwdMenu = { el: this.DOM.el.querySelector('.header-yama__nav-mobile')};
		this.DOM.rwdMenu.inner = this.DOM.el.querySelector('.header-yama__nav-mobile-inner');

		this.initEvents();
	}

    /**
     * Initializes some events.
     */
	initEvents(){
		this.animateOnScroll();

		this.DOM.hamburgerCtrl.el.addEventListener('click', () => {
			if ( !this.headerStatus.isOpen ) {
				this.open();
			} else {
				this.close();
			}
		})

		document.addEventListener("keydown", e => {
			if ( e.key === 'Escape' ) {
				if ( this.headerStatus.isOpen ) {
					this.close();
				}
			}
		})
	}

    /**
     * ScrollTrigger animations for scrolling
     */
	animateOnScroll() {
		const showAnim = gsap.from(this.DOM.el, {
			yPercent: -100,
			paused: true,
			duration: 0.4
		})
		.progress(1)

		ScrollTrigger.create({
			start: "top top",
			end: 99999,
			onUpdate: (self) => {
			  self.direction === -1 ? showAnim.play() : showAnim.reverse()
			}
		});
	}

    /**
     * Opens the menu
     */
	open(){
		if ( this.headerStatus.isOpen ) return;
		this.headerStatus.isOpen = true;

		this.headerTimeline = gsap.timeline({
			defaults: {
				duration: 0.3,
				ease: 'power3',
			},
		})
		.addLabel('start', 0)
		.add(() => {
			this.DOM.el.classList.add('header-yama--open')
		}, 'start' )
		.to(this.DOM.hamburgerCtrl.lineTop, {
			translateY: '7px',
			rotation: 45
		}, 'start')
		.to(this.DOM.hamburgerCtrl.lineMiddle, {
			opacity: 0
		}, 'start')
		.to(this.DOM.hamburgerCtrl.lineBottom, {
			translateY: '-7px',
			rotation: -45
		}, 'start')

		.addLabel('content', 0.1)
		.to(this.DOM.rwdMenu.el, {
			height: '100vh',
			display: 'flex',
			ease: 'expo.in',
		}, 'content' )
		.to(this.DOM.rwdMenu.inner, {
			opacity: 1,
			ease: 'expo.in',
		}, 'content+=0.1')
	}

    /**
     * Close the menu
     */
	close(){
		if ( !this.headerStatus.isOpen ) return;
		this.headerStatus.isOpen = false;

		this.headerTimeline = gsap.timeline({
			defaults: {
				duration: 0.3,
				ease: 'power3',
			},
		})
		.addLabel('start', 0)
		.add(() => {
			this.DOM.el.classList.remove('header-yama--open')
		}, 'start' )
		.to(this.DOM.hamburgerCtrl.lineTop, {
			duration: 0.1,
			translateY: '0',
			rotation: 0
		}, 'start')
		.to(this.DOM.hamburgerCtrl.lineMiddle, {
			opacity: 1
		}, 'start')
		.to(this.DOM.hamburgerCtrl.lineBottom, {
			translateY: '0px',
			rotation: 0
		}, 'start')

		.addLabel('content', 0.1)
		.to(this.DOM.rwdMenu.el, {
			height: '0vh',
			display: 'none',
			ease: 'expo.out',
		}, 'content' )
		.to(this.DOM.rwdMenu.inner, {
			opacity: 0,
			ease: 'expo.out',
		}, 'content-=0.1')
	}
}

For a live demonstration, you can visit this link.

That’s all for creating a sticky navigation using Tailwind CSS and GSAP. You may also want to check out my other tutorials.

Resources


Rizwan Aritonang

An independent WordPress & Front-End Developer from Bandung, Indonesia.

Get In Touch

Leave a Comment