A fixed navigation with smooth, jQuery powered scroll. Nothing fancy here, yet a handy snippet for creating a secondary menu to quickly surf through the page content. We see this effect a lot these days, a good example I can think of is Disqus For Websites. A nice touch is to animate the logo and the main call-to-action button to slide in once the navigation becomes fixed.
Icons: Nucleo
Creating the structure
We created a section#cd-intro
to wrap our intro image, tagline and call-to-action button.
The secondary navigation is an unordered list inserted in the .cd-secondary-nav
element. All the remaining content has been placed in the .cd-main-content
element.
<section id="cd-intro">
<div id="cd-intro-tagline">
<h1><!-- your tagline here --></h1>
<a href="#0" class="cd-btn"><!-- your action button text here --></a>
</div>
</section>
<div class="cd-secondary-nav">
<a href="#0" class="cd-secondary-nav-trigger">Menu<span></span></a> <!-- button visible on small devices -->
<nav>
<ul>
<li>
<a href="#cd-placeholder-1">
<b>Services</b>
<span></span><!-- icon -->
</a>
</li>
<!-- other items here -->
</ul>
</nav>
</div> <!-- .cd-secondary-nav -->
<main class="cd-main-content">
<section id="cd-placeholder-1" class="cd-section cd-container">
<!-- your section content here-->
</section> <!-- #cd-placeholder-1 -->
<section id="cd-placeholder-2" class="cd-section cd-container">
<!-- your section content here-->
</section> <!-- #cd-placeholder-2 -->
<!-- other sections here -->
</main> <!-- .cd-main-content -->
Adding style
Since we coded this resource starting from mobile, we assigned a position: fixed
to the unordered list inside the .cd-secondary-nav
, and placed it at the bottom-right of the viewport. When user taps the .cd-secondary-nav-trigger,
we assign the class .is-visible
to the unordered list, changing its CSS3 Scale value from 0 to 1.
When the viewport is larger that 1170px, we hide the .cd-secondary-nav-trigger
and change the position of the unordered list from fixed to static, so that it is visible inside the .cd-secondary-nav
, right after the section#cd-intro
.
.cd-secondary-nav ul {
position: fixed;
right: 5%;
bottom: 20px;
visibility: hidden;
transform: scale(0);
transform-origin: 100% 100%;
transition: transform 0.3s, visibility 0s 0.3s;
}
.cd-secondary-nav ul.is-visible {
visibility: visible;
transform: scale(1);
transition: transform 0.3s, visibility 0s 0s;
}
@media only screen and (min-width: 1170px) {
.cd-secondary-nav ul {
/* reset navigation values */
position: static;
width: auto;
max-width: 100%;
visibility: visible;
transform: scale(1);
}
}
.cd-secondary-nav-trigger {
position: fixed;
bottom: 20px;
right: 5%;
width: 44px;
height: 44px;
}
@media only screen and (min-width: 1170px) {
.cd-secondary-nav-trigger {
display: none;
}
}
When the user scrolls more than the section#cd-intro
height, we assign the .is-fixed
class to the .cd-secondary-nav
, changing its position from relative to fixed and reducing its height, and then we add the .animate-children
class to animate its children. We couldn't use a single class due to a Firefox bug (CSS transition animation fails when parent element changes position attribute). More info about this in the Events handling section below.
@media only screen and (min-width: 1170px) {
.cd-secondary-nav.is-fixed {
position: fixed;
left: 0;
top: 0;
height: 70px;
width: 100%;
}
.cd-secondary-nav li a {
padding: 58px 40px 0 40px;
transition: padding 0.2s;
}
.cd-secondary-nav li a span {
transition: opacity 0.2s;
}
.cd-secondary-nav.animate-children li a {
padding: 26px 30px 0 30px;
}
.cd-secondary-nav.animate-children li a span {
opacity: 0;
}
}
We also wanted to show the logo and the call-to-action button when the secondary navigation is fixed. To do so we defined two classes: .is-hidden
and .slide-in
(the first is assigned when user scrolls more than the #cd-intro-tagline
bottom, the second more that .cd-secondary-nav
top).
@media only screen and (min-width: 1170px) {
#cd-logo.is-hidden {
/* assign a position fixed and move outside the viewport (on the left) */
opacity: 0;
position: fixed;
left: -20%;
transition: left 0.3s, opacity 0.3s;
}
#cd-logo.is-hidden.slide-in {
/* slide in when the secondary navigation gets fixed */
left: 5%;
opacity: 1;
}
.cd-btn.is-hidden {
/* assign a position fixed and move outside the viewport (on the right) */
opacity: 0;
position: fixed;
right: -20%;
transition: right 0.3s, opacity 0.3s;
}
.cd-btn.is-hidden.slide-in {
/* slide in when the secondary nav gets fixed */
right: 5%;
opacity: 1;
}
}
Events Handling
When user scrolls more than the secondary navigation offset top, we assign it the .is-fixed
class to change its position value; we add the .animate-children
class with a 50ms delay (due to a Firefox bug) in order to animate its children. Therefore the position value change won't affect the transition, since the they don't happen at the same time.
var secondaryNav = $('.cd-secondary-nav'),
secondaryNavTopPosition = secondaryNav.offset().top;
$(window).on('scroll', function(){
if($(window).scrollTop() > secondaryNavTopPosition ) {
secondaryNav.addClass('is-fixed');
setTimeout(function() {
secondaryNav.addClass('animate-children');
$('#cd-logo').addClass('slide-in');
$('.cd-btn').addClass('slide-in');
}, 50);
} else {
secondaryNav.removeClass('is-fixed');
setTimeout(function() {
secondaryNav.removeClass('animate-children');
$('#cd-logo').removeClass('slide-in');
$('.cd-btn').removeClass('slide-in');
}, 50);
}
});