Responsive stick-to-top menu
Reacts to viewport resizing and zoom appropriately.
HTML
<body>
<header id="banner">
<div class="banner--hero">
<h1 id="site-title">Page Title</h1>
</div>
<nav id="nav">
<ul class="nav-items">
<li class="nav-item"><a class="nav-item__link" href="#">Foo</a>
</li>
<li class="nav-item"><a class="nav-item__link" href="#">Bar</a>
</li>
<li class="nav-item"><a class="nav-item__link" href="#">Baz</a>
</li>
</ul>
</nav>
<img src="#" alt="Here be your hero image" class="banner-image" width="300" height="300" />
</div>
</header>
<div id="content" role="main">
<h1>Scroll and resize!</h1>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.</p>
<ul>
<li>Excepteur sint</li>
<li>occaecat cupidatat non proident,</li>
<li>sunt in culpa qui</li>
<li>officia deserunt mollit anim id est laborum.</li>
</ul>
<h2>Now, at least, you should try to zoom out until the menu sticks to the top again</h2>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna...
CSS
body {
font-size: 1.2em;
line-height: 1.6em;
background: #ededed;
}
#banner {
position: relative;
background: #ccc;
}
#site-title {
font-weight: normal;
font-size: 2em;
position: absolute;
bottom: 15%;
}
#nav {
width: 100%;
background: rgba(255, 255, 0, 0.8);
position: absolute;
padding: 0;
bottom: 0px;
height: 3em;
overflow: hidden;
-webkit-transition: background-color 0.2s;
-moz-transition: background-color 0.2s;
-o-transition: background-color 0.2s;
}
#nav.docked {
position: fixed;
top: 0px;
background-color: rgba(0, 0, 0, 0.6);
}
#nav.docked a.nav-item__link {
color: #fff;
}
#nav.docked .nav-item a:hover {
background-color: rgba(0, 0, 0, 0.3);
}
.nav-items {
padding-left: 0;
margin: 0;
}
.nav-item {
display: inline;
}
a.nav-item__link {
display: inline-block;
line-height: 3em;
padding: 0 0.5em;
background-color: transparent;
border-bottom-color: transparent;
margin-right: 1em;
-webkit-transition: color 0.2s;
-moz-transition: color 0.2s;
-o-transition: color 0.2s;
}
.nav-item:first-child a.nav-item__link {
margin-left: -0.5em;
/* padding */
}
.nav-item a:hover {
background-color: rgba(255, 255, 255, 0.8);
}
JavaScript
var windowScrollTop = function () {
return window.pageYOffset;
};
var Menu = (function (scrollOffset) {
var Menu = function () {
this.element = document.getElementById('nav');
this.docked = false;
this.initialOffsetTop = 0;
this.resetInitialOffsetTop();
}
Menu.prototype = {
offsetTop: function () {
return this.element.offsetTop;
},
resetInitialOffsetTop: function () {
this.initialOffsetTop = this.offsetTop();
},
dock: function () {
this.element.className = 'docked';
this.docked = true;
},
undock: function () {
this.element.className = this.element.className.replace('docked', '');
this.docked = false;
},
toggleDock: function () {
if (this.docked === false && (this.offsetTop() - scrollOffset() < 0)) {
this.dock();
} else if (this.docked === true && (scrollOffset() <= this.initialOffsetTop)) {
this.undock();
}
}
};
return Menu;
})(windowScrollTop);
var menu = new Menu();
window.onscroll = function () {
menu.toggleDock();
};
var updateMenuTop = function () {
// Shortly dock to reset the initial Y-offset
menu.undock();
menu.resetInitialOffsetTop();
// If appropriate, undock again based on the new value
menu.toggleDock();
};
var zoomListeners = [updateMenuTop];
(function () {
var w = window,
d = document,
e = d.documentElement,
g = d.getElementsByTagName('body')[0];
var lastWidth = 0;
function pollZoomFireEvent() {
var widthNow = w.innerWidth || e.clientWidth || g.clientWidth;
if (lastWidth == widthNow) {
return;
}
lastWidth = widthNow;
// Length changed, user must have zoomed, invoke listeners.
for (i = zoomListeners.length - 1; i >= 0; --i) {
...