Go to homepage

Projects /

Vertical Fixed Navigation #2

A smart vertical navigation, with round indicators that turn into labelled icons when the user interacts with them.

Vertical Fixed Navigation #2
Check our new component library →

Our first concept of vertical fixed navigation is one of our most popular resources. This time, we tried to push this concept a little further.

The basic idea behind putting round indicators on the side of a web page, is to give a hint to the user about the number of sections she/he can go through. We think of each dot as a content chapter, with its own title. Usually, users have to hover over a dot to access the title.

In an attempt to simplify this pattern, we decided to transform the dots when the user interacts with them, by scaling them up and showing an icon + label. Users don’t need to select a specific dot/item, but just move to the side, thus showing their willingness to access the navigation.

Here is a quick preview of the final result (created using After Effects):

v-nav-animation

Icons: Nucleoapp.com

Creating the structure

Our navigation is an unordered list wrapped in a nav.cd-vertical-nav. A button.cd-nav-trigger is used to open the navigation on small devices.
Besides, a section.cd-section has been created for each navigation item.

<nav class="cd-vertical-nav">
   <ul>
      <li><a href="#section1" class="active"><span class="label">Intro</span></a></li>
      <li><a href="#section2"><span class="label">Events</span></a></li>
      <!-- additional navigation items here -->
   </ul>
</nav><!-- .cd-vertical-nav -->

<button class="cd-nav-trigger cd-image-replace">Open navigation<span aria-hidden="true"></span></button>

<section id="section1" class="cd-section">
   <div class="content-wrapper">
      <h1>Vertical Fixed Navigation #2</h1>
      <a href="#section2" class="cd-scroll-down cd-image-replace">scroll down</a>
   </div>
</section><!-- cd-section -->

<section id="section2" class="cd-section">
   <div class="content-wrapper">
      <!-- section content here -->
   </div>
</section><!-- cd-section -->

<!-- additional sections here -->

Adding style

On small devices (viewport width smaller than 800px), we set a position: fixed for the .cd-nav-trigger and <nav> elements and  placed them at the bottom-right corner of the page; we then scale down the navigation, using the bottom-right corner as transform origin.

When user clicks on the .cd-nav-trigger element, we give the .open class to the navigation to change its scale value from 0 to 1, with a CSS3 transition to achieve a smooth animation.

.cd-nav-trigger {
  display: block;
  position: fixed;
  z-index: 2;
  bottom: 30px;
  right: 5%;
}

.cd-vertical-nav {
  position: fixed;
  z-index: 1;
  right: 5%;
  bottom: 30px;
  transform: scale(0);
  transform-origin: right bottom;
  transition: transform 0.2s;
}
.cd-vertical-nav.open {
  transform: scale(1);
}

On bigger devices, we use Modernizr to detect touch and no-touch devices (using.touch and .no-touch classes).
On touch devices, the lateral navigation items (labels and icons) are visible by default, while on no-touch devices they are shown when the user hovers over the lateral navigation.

We set a fixed height and width for the <nav> element, and place it on the right side of the viewport. We use its ::before pseudo-element to create the navigation background; on no-touch devices only, the ::before element is, by default, translated to the right (outside the viewport) and is moved back to its original position when the user hovers over the navigation. The same happens for the span.label elements.

@media only screen and (min-width: 800px) {
  .cd-vertical-nav {
    right: 0;
    top: 0;
    height: 100vh;
    width: 90px;
  }
  .cd-vertical-nav::before {
    /* this is the navigation background */
    content: '';
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background: rgba(0, 0, 0, 0.8);
    transform: translateX(100%);
    transition: transform 0.4s;
  }
  .no-touch .cd-vertical-nav:hover::before, 
  .touch .cd-vertical-nav::before {
    transform: translateX(0);
  }
  .cd-vertical-nav .label {
    display: block;
    transform: translateX(100%);
    transition: transform 0.4s;
  }
  .no-touch .cd-vertical-nav:hover .label, 
  .touch .cd-vertical-nav .label {
    transform: translateX(0);
  }
}

To create the navigation item icons and dots, we use, respectively, the ::after and ::before pseudo-elements of the navigation anchor elements (<a>). On no-touch devices only, the ::after and ::before are scaled down by default and then scaled back up when the user hovers over the navigation.

@media only screen and (min-width: 800px) {
  .cd-vertical-nav a {
    position: relative;
    padding: 3em 0 0;
    margin: 1.4em auto;
  }
  .cd-vertical-nav a::before, 
  .cd-vertical-nav a::after {
    /* used to create the filled circle and the background icon */
    content: '';
    position: absolute;
    left: 50%;
    transition: transform 0.4s 0s;
  }
  .cd-vertical-nav a::before {
    /* filled circle */
    top: 0;
    height: 32px;
    width: 32px;
    border-radius: 50%;
    background: #eaf2e3;
    transform: translateX(-50%) scale(0.25);
  }
  .cd-vertical-nav a::after {
    /* icon */
    top: 8px;
    height: 16px;
    width: 16px;
    background: url(../img/cd-nav-icons.svg) no-repeat;
    transform: translateX(-50%) scale(0);
  }
  .no-touch .cd-vertical-nav:hover a::before, 
  .no-touch .cd-vertical-nav:hover a::after, 
  .touch .cd-vertical-nav li:nth-of-type(n) a::before, 
  .touch .cd-vertical-nav li:nth-of-type(n) a::after {
    transform: translateX(-50%) scale(1);
  }
}

Now the tricky part: when the navigation dots are scaled down, they are too distant one from the other. We can reduce this distance translating them along the Y axis.

Let's start from the central dots (in our case, the second and the third); we want to translate down the second one (so we have to use a positive translate value), while we want to translate up the third one (so we have to use a negative translate value). In our case we have:

.cd-vertical-nav li:nth-of-type(2) a::after {
  transform: translateX(-50%) translateY(1.5em) scale(0);
}
.cd-vertical-nav li:nth-of-type(2) a::before {
  transform: translateX(-50%) translateY(1.5em) scale(0.25);
}
.cd-vertical-nav li:nth-of-type(3) a::after {
  transform: translateX(-50%) translateY(-1.5em) scale(0);
}
.cd-vertical-nav li:nth-of-type(3) a::before {
  transform: translateX(-50%) translateY(-1.5em) scale(0.25);
}

Then, the translate value for the first dot is gonna be three times the one of the second dot, and the same for the fourth one (three times the translate value of the third dot).

.cd-vertical-nav li:first-of-type a::after {
  transform: translateX(-50%) translateY(4.5em) scale(0);
}
.cd-vertical-nav li:first-of-type a::before {
  transform: translateX(-50%) translateY(4.5em) scale(0.25);
}
.cd-vertical-nav li:nth-of-type(4) a::after {
  transform: translateX(-50%) translateY(-4.5em) scale(0);
}
.cd-vertical-nav li:nth-of-type(4) a::before {
  transform: translateX(-50%) translateY(-4.5em) scale(0.25);
}

If you have a different number of navigation items, you have to change these translate values accordingly. For example, if you have six items, starting again from the central dots (in this case, the third and the fourth), you can assign them a translateY value of 1.5 em/-1.5em; then to the second and the fifth a translateY value of 4.5em/-4.5em (3*1.5), and finally to the first and sixth a translateY value of 7.5em/-7.5em (5*1.5).

If you have an odd number of items, let's say 5, you do not translate the central one (in this case the third one). You then assign a translateY value of 3em/-3em(2*1.5) to the second and the fourth dots, and finally a translateY value of 6em/-6em(4*1.5) to the first and fifth dots.

Events handling

When user scrolls through the sections, the updateSections() function evaluates which section is currently being viewed and assigns the .active class to the corresponding navigation item.
Besides, we listen to the click event on the button.cd-nav-trigger to open/close the navigation on small devices.

Project duplicated

Project created

Globals imported

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