Go to homepage

Projects /

Rounded Animated Navigation

An experimental full-screen navigation, animated using CSS and jQuery, that expands within a circle.

Rounded Animated Navigation
Check our new component library →

I was checking out the new Ping iOS app. The effect when you tap on the circle to switch the page is too cool, so I tried to create something similar in CSS and jQuery. The result is pretty cool, hence we decided to release the snippet here on CodyHouse :) It's an experimental navigation, yet it's supported by all major browsers - we used Velocity.js - and I'm sure you guys will find a creative way to use this!

Creating the structure

About the HTML structure, we used an unordered list for the navigation, semantically wrapped into a <nav> element. The .cd-overlay-nav and .cd-overlay-content are used to animate the round shape. Precisely they are used as containers, to position the circles right behind the top-right menu icon, while the <span> elements inside are the 2 colored circles that expand. Finally the .cd-nav-trigger is the menu icon that animates into a cross icon.

<header>
   <a class="cd-logo" href="#0"><img src="img/cd-logo.svg" alt="Logo"></a>
</header>

<nav>
   <ul class="cd-primary-nav">
      <li><a href="#0">The team</a></li>
      <li><a href="#0">Our services</a></li>
      <li><a href="#0">Our projects</a></li>
      <li><a href="#0">Start a project</a></li>
      <li><a href="#0">Join In</a></li>
      <li><a href="#0">Create an account</a></li>
   </ul>
</nav>

<main class="cd-content">
   <!-- your content here -->
</main> 

<div class="cd-overlay-nav">
   <span></span>
</div> <!-- cd-overlay-nav -->

<div class="cd-overlay-content">
   <span></span>
</div> <!-- cd-overlay-content -->

<a href="#0" class="cd-nav-trigger">Menu<span class="cd-icon"></span></a>

Adding style

If you look at the CSS, you will notice the trigger button (.cd-nav-trigger) is in position:fixed on the top right. For the effect to work properly, we had to center the containers (.cd-overlay-nav and .cd-overlay-content) of the 2 animated circles right behind the trigger. To do such a thing, we needed to define a fixed height and width for both of them. But if you look at the <span> elements - which actually are the animated circles, you may notice we didn't define any positioning or size value. Since the circle has to cover the entire screen, regardless the viewport size, we define the positioning (top and left values) and the size (width) in jQuery.

.cd-nav-trigger {
  top: 18px;
  right: 5%;
  height: 44px;
  width: 44px;
  z-index: 5;
  /* image replacement */
  overflow: hidden;
  text-indent: 100%;
  white-space: nowrap;
}

.cd-overlay-nav, .cd-overlay-content {
  /* containers of the 2 main rounded backgrounds - these containers are used to position the rounded bgs behind the menu icon */
  position: fixed;
  top: 18px;
  right: 5%;
  height: 4px;
  width: 4px;
  transform: translateX(-20px) translateY(20px);
}

.cd-overlay-nav span, .cd-overlay-content span {
  display: inline-block;
  position: absolute;
  border-radius: 50%;
  will-change: transform;
  transform: scale(0);
}

The idea was to animate the menu icon, not just replace the "hamburger" with a "close" icon,  so we added a <span> element (.cd-icon) inside the trigger. This way we can create - and animate - the icon using the ::before and ::after pseudo-elements.

.cd-nav-trigger .cd-icon {
  /* icon created in CSS */
  position: absolute;
  left: 50%;
  top: 50%;
  bottom: auto;
  right: auto;
  transform: translateX(-50%) translateY(-50%);
  display: inline-block;
  width: 18px;
  height: 3px;
  background-color: #ffffff;
  z-index: 10;
}

.cd-nav-trigger .cd-icon::before, .cd-nav-trigger .cd-icon:after {
  /* upper and lower lines of the menu icon */
  position: absolute;
  top: 0;
  right: 0;
  width: 100%;
  height: 100%;
  background-color: inherit;
  transition: transform .3s;
}

.cd-nav-trigger .cd-icon::before {
  transform: translateY(-6px) rotate(0deg);
}

.cd-nav-trigger .cd-icon::after {
  transform: translateY(6px) rotate(0deg);
}

.cd-nav-trigger.close-nav .cd-icon {
  /* user clicks on the .cd-nav-trigger element - transform the icon */
  background-color: rgba(255, 255, 255, 0);
}

.cd-nav-trigger.close-nav .cd-icon::before, .cd-nav-trigger.close-nav .cd-icon::after {
  background-color: white;
}

.cd-nav-trigger.close-nav .cd-icon::before {
  transform: translateY(0) rotate(45deg);
}

.cd-nav-trigger.close-nav .cd-icon::after {
  transform: translateY(0) rotate(-45deg);
}

The menu/trigger background-color changes and it's animated with a slight delay. Once again, we used 2 pseudo elements to achieve this effect:

.cd-nav-trigger::before, .cd-nav-trigger::after {
  /* 2 rounded colored backgrounds for the menu icon */
  position: absolute;
  top: 0;
  left: 0;
  border-radius: 50%;
  height: 100%;
  width: 100%;
  transition-property: transform;
}
.cd-nav-trigger::before {
  background-color: #091d23;
  transform: scale(1);
  transition-duration: 0.3s;
  transition-delay: 0.4s;
}
.cd-nav-trigger::after {
  background-color: #ffb441;
  transform: scale(0);
  transition-duration: 0s;
  transition-delay: 0s;
}
.cd-nav-trigger.close-nav::before {
  /* user clicks on the .cd-nav-trigger element - 1st rounded background disappears */
  transform: scale(0);
}
.cd-nav-trigger.close-nav::after {
  /* user clicks on the .cd-nav-trigger element - 2nd rounded background appears */
  transform: scale(1);
  transition-duration: 0.3s;
  transition-delay: 0.4s;
}

Events handling

Before diving into the jQuery, I'd like to explain what happens in few simple words: when user clicks/taps on the menu button, we animate the <span> inside the .cd-overlay-nav. Once the circle fills the screen, we show the navigation. When user clicks/taps again on the trigger to close the navigation, we animate the <span> inside the .cd-overlay-content. At the end of this animation, we hide the first circle and the navigation - which are still there, just not visible because they have a lower z-index. Finally we reduce the opacity of the second round shape (yellow in the demo), thus revealing the content - and faking that the content is on top of the navigation.

About the jQuery, we defined position and size of the <span> circles element (using the initLayer() function). We assigned a height equal to twice the viewport diagonal, and a top (and left) equal to negative value the viewport diagonal  (the <span> is inside the .cd-overlay-nav/.cd-overlay-content and has a position: absolute).

When user first clicks the .cd-nav-trigger, we animate the .cd-overlay-nav span element, changing its scale value from 0 to 1 (we used Velocity.js for the animation):

var overlayNav = $('.cd-overlay-nav'),
    toggleNav = $('.cd-nav-trigger'),
    navigation = $('.cd-primary-nav');

toggleNav.on('click', function(){
   if(!toggleNav.hasClass('close-nav')) {
      //animate menu icon into a cross icon
      toggleNav.addClass('close-nav');
      //animate the navigation layer
      overlayNav.children('span').velocity({
         translateZ: 0,
         scaleX: 1,
         scaleY: 1,
      }, 500, 'easeInCubic', function(){
         //show navigation
         navigation.addClass('fade-in');
      });
   }
}

When user clicks the .cd-nav-trigger to close the menu, we animate the .cd-overlay-content span scale value instead:

var overlayNav = $('.cd-overlay-nav'),
    overlayContent = $('.cd-overlay-content'),
    toggleNav = $('.cd-nav-trigger'),
    navigation = $('.cd-primary-nav');

toggleNav.on('click', function(){
    if(!toggleNav.hasClass('close-nav')) {
       //it means navigation is not visible yet - open it and animate navigation layer
       //....
    } else {
       //animate cross icon into a menu icon
       toggleNav.removeClass('close-nav');
       //animate the content layer
       overlayContent.children('span').velocity({
          translateZ: 0,
          scaleX: 1,
          scaleY: 1,
       }, 500, 'easeInCubic', function(){
          //hide navigation
          navigation.removeClass('fade-in');
          //scale to zero the navigation layer
          overlayNav.children('span').velocity({
             translateZ: 0,
             scaleX: 0,
             scaleY: 0,
          }, 0);
          //reduce to opacity of the content layer with the is-hidden class
          overlayContent.addClass('is-hidden').one('webkitTransitionEnd otransitionend oTransitionEnd msTransitionEnd transitionend', function(){
             //wait for the end of the transition and scale to zero the content layer
             overlayContent.children('span').velocity({
                translateZ: 0,
                scaleX: 0,
                scaleY: 0,
             }, 0, function(){overlayContent.removeClass('is-hidden')});

          });
       });
    }
});

Project duplicated

Project created

Globals imported

There was an error while trying to export your project. Please try again or contact us.