Vertical sidebar menus are great. They allow for much more content than horizontal menus, as they can grow past the bottom of the page. For this reason, sites with long pages of documentation such as Zurb, Bourbon, Really Good Emails and Bootstrap tend to employ the vertical navigation design pattern.
Keep in mind that I’m not talking about top-level primary site navigation. Instead, I’m specifically talking about page-level vertical sidebar menus.
A New Take on an Old Friend
Vertical navigation has existed pretty much since the birth of the internet. Though recently, a few design pattern variations have emerged, including the sticky sidebar (Bootstrap) and the scrolling container (Really Good Emails). The scrolling container is self-explanatory: users scroll the menu independently of the main content, and the sidebar is not affected by the scroll position of the main page. In contrast, as users scroll down the page on Bootstrap, the menu sticks to the side as a fixed position css element, and reacts to the user’s scroll position. This latter method creates a stronger “bond” between the sidebar and the content, as clicking on sidebar items will scroll to different parts of the page.
In addition to the sticky nav, Bootstrap does something else that’s very clever: it detects the users’ scroll position, and expands different parts of the sidebar. However, as I’m on a small screen, expanding submenus ends up pushing the rest of the menu outside my browser, pushing some items below the viewport.
A vertical nav falls apart if there are too many links on the menu. If it’s too tall, links will appear below the fold. And if it’s sticky, users will never see those f links.
In this case, one solution is to allow the viewport to keep scrolling continue scrolling until the bottom of the menu is visible, and stick the menu to the viewport as the browser continues scrolling down. Likewise, when scrolling up, let the menu scroll with the content until the top of the menu is visible before making the menu stick. This method will let users to quickly access both the top and the bottom of the navigation menu.
The one down-side to this implementation is the user won’t always see current active menu item, since if the menu is very tall, the active link might either be above or below the viewport.
I’ve seen a few implementations of this idea in the wild, but can’t remember where. I’ll update this post with examples when I find any. Or you could spare me the trouble and send me some.
There are a few scenarios I used when I built the demo:
- Is the sidebar always fairly short?
- Is the sidebar most likely going to be very tall?
- Could the sidebar be either short or tall?
1) If the sidebar is usually short, it will most likely never be larger than the browser viewport. In this scenario, the sidebar could easily remain a fixed-position element.
2) If the sidebar is always taller than the viewport, we can let the viewport scroll to the bottom or the top edge of sidebar menu before applying a sticky effect, This way, every part of the sidebar will be easier to access.
3) This case is a combination of the first two cases. The menu behavior needs to respond to the dynamic size of the navigation element. It adds a small amount of complication, but still adheres to items 1) and 2). This is the scenario that’s covered by this demo page.
To approach scenario 3, I used JQuery Waypoints to track the positions of menu objects relative to both the viewport and other objects. A much simpler way to implement a sticky menu is using Skrollr, but Skrollr lacks the dynamic properties of Waypoints. It would be a good technology for scenario 1, if the menu height and the waypoints weren’t dynamic.
The implementation using Waypoints is fairly straightforward. On every element change or viewport interaction (like the size of the menu changing, the page is scrolled, or the browser resized) I update the relative waypoint offsets, and the positions where the menu should stick. I’m using Waypoints to track for the scroll condition, and set the appropriate values when a certain scroll condition has been met.
I also make sure to implement deduplication and other barriers to prevent the system from calculating more than it needs to, to keep the scrolling mechanism as smooth as possible.
Bugs and Issues
After several code rewrites and iterations, most bugs have been squashed. Though there are still a few bugs:
1) The way I’m implementing the debouncer to reduce the number of calculations doesn’t always seem to catch user behavior, like scrolling or resizing.
2) I’m not sure how to account for zooming in the browser, since I don’t know how to capture browser behavior.
3) I’m accounting for page focus and page resize, but I’m not accounting for repositioning the menu while the browser window is being resized. At the end of window resize, the menu position will be wrong until the viewport hits another waypoint.
4) Finally, this method will not work properly on mobile and tablet devices, as scrolling won’t fire until the scrolling stops. ScrollMagic seems to have bypassed this issue by using iScroll, but at this point it might make more sense to use an independent sidebar or an off-canvas pattern.
If you have found issues or have any ideas to how to speed up, improve, or fix anything, please send me a note!
Sometimes a little more effort is required to add subtle pieces of details, something most might not even recognize, but the extra effort definitely can make a product or functionality shine.
All the code for this repository is free to use. It is experimental and a proof of concept, and is (only somewhat) optimized or checked for bugs. If you’d like to add this functionality to your site, you’re more than welcome.
In the future I’ll create a separate Github project with a guide on how to use the code.