Expand­ing menu with GSAP

Publisher: TJ Fogarty

Modified: 2018-03-13

This is a straight­for­ward enough bit of code that will allow you to cre­ate a re-usable expand­ing menu.

There are many, many ways you could go about this. I fig­ured a sim­ple Object would be enough for this example.

See the Pen Expand­ing Nav­i­ga­tion by T.J. Fog­a­r­ty (@tjFogarty) on Code­Pen.


We’ll start with the mark-up. The most impor­tant bits are:

  • Class of js-nav-toggle on our trigger
  • data-nav-id also on our trigger
  • Cor­re­spond­ing id on our nav ele­ment which cor­re­sponds with data-nav-id

Our script watch­es js-nav-toggle for a click, and fig­ures out the nav­i­ga­tion from it’s data-nav-id attribute.

<div class="container">

  <a href="#" data-nav-id="main-nav" class="c-nav--trigger js-nav-toggle">Primary Menu</a>

  <nav class="c-nav--main" role="navigation" id="main-nav">

    <ul class="c-nav__list">
      <li class="c-nav__item"><a class="c-nav__link" href="#">Home</a></li>
      <li class="c-nav__item"><a class="c-nav__link" href="#">About</a></li>
      <li class="c-nav__item"><a class="c-nav__link" href="#">Clients</a></li>
      <li class="c-nav__item"><a class="c-nav__link" href="#">Contact Us</a></li>
    </ul>

  </nav>

  <a href="#" data-nav-id="secondary-nav" class="c-nav--trigger js-nav-toggle">Secondary Menu</a>

  <nav class="c-nav--secondary" role="navigation" id="secondary-nav">

    <ul class="c-nav__list">
      <li class="c-nav__item"><a class="c-nav__link" href="#">Home</a></li>
      <li class="c-nav__item"><a class="c-nav__link" href="#">About</a></li>
      <li class="c-nav__item"><a class="c-nav__link" href="#">Clients</a></li>
      <li class="c-nav__item"><a class="c-nav__link" href="#">Contact Us</a></li>
    </ul>

  </nav>

  <h3>Title</h3>

  <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Nemo aliquid sunt rerum dignissimos voluptatem ex blanditiis nisi consequatur repudiandae quisquam quos error quam optio, dicta nesciunt neque, et reiciendis omnis.</p>

</div>

In terms of styles, I’ve set the nav­i­ga­tions to display: none; which you can glean from the Code­Pen embed above. Every­thing else is just to make it look some­what presentable.

The script has been doc­u­ment­ed, and we’re using TweenLite.fromTo(el, duration, from, to); to ani­mate them back and forth depend­ing on their active state.

/* globals $, TweenLite */

'use strict';

/**
 * Navigation controller for expanding navs
 * @type {Object}
 */
var NavController = {
  $trigger: $('.js-nav-toggle'), // these trigger open/close
  // Animation settings
  animate: {
    duration: 0.3,

    visible: {
      display: 'block',
      autoAlpha: 1,
      height: 0 // this is calculated correctly later
    },

    hidden: {
      display: 'none',
      autoAlpha: 0,
      height: 0
    }
  },
  attrs: {
    id: 'data-nav-id'
  },
  classes: {
    active: 'is-active'
  },

  /**
   * Kick things off
   */
  init: function() {
    this.bindUI();
  },

  /**
   * Watch for events
   */
  bindUI: function() {
    this.$trigger.on('click', this.handleClick);
  },

  /**
   * Show/hide nav based on click
   * @param  {Event} e
   */
  handleClick: function(e) {
    var _ = NavController,
        $trigger = $(this),
        navId = $trigger.attr(_.attrs.id),
        $nav = $('#' + navId);

    e.preventDefault();

    $nav.toggleClass(_.classes.active);

    // Fetch correct height to animate to and from
    _.animate.visible.height = $nav.outerHeight();

    if(_.isNavOpen($nav)) {
      $trigger.addClass(_.classes.active);
      TweenLite.fromTo($nav, _.animate.duration, _.animate.hidden, _.animate.visible);
    } else {
      $trigger.removeClass(_.classes.active);
      TweenLite.fromTo($nav, _.animate.duration, _.animate.visible, _.animate.hidden);
    }
  },

  /**
   * Check if given nav is open/closed
   * @param  {DOM Element}  $nav 
   * @return {Boolean}
   */
  isNavOpen: function($nav) {
    return $nav.hasClass(this.classes.active);
  }
};

NavController.init();