Bootstrap 3 Affix

Creating a Bootstrap Affix can be a challenging task. This tutorial will help you get it working.

The Bootstrap 3 Affix is a navigation affix-ed (= attached) to the content on the same web page. The affix will reflect which part of the content the user is reading/looking at. But the affix can also be used to jump to a certain section in the content.

Bootstrap 3 Affix

The Affix that we’re going to create can be seen and tried in this
DEMO

When you scroll the demo page you will see that the navigation top left indicates where you are, which section currently sits at top of the window. This is the essence of what a Bootstrap Affix does.

I have only included items in the navigation for the first two content sections (Cupcake Lorem Ipsum and Veggie Lorem Ipsum). Each of these two sections contains smaller sub sections (e.g. Sweet Jelly, Tiramisu).

Affix vs Scrollspy

The Affix and the Scrollspy have a lot in common: they both spy on the scroll position and show this position in a navigation.

The difference is:

  • the Scrollspy is used for a small content section and that section has its own scrollbar
  • the Affix is used for a large amount of content and the scrollbar is part of the window that displays the page.

Affix parts and issues

A Bootstrap Affix consists of two parts:

  1. A (usually large) content section divided into sub sections
  2. The navigation affixed to the content, also knows as the Affix

Seems pretty straightforward. No reason to suspect that building one can be a bit complex. And in my opinion the explanation about the Affix provided on the Bootstrap website is really inadequate.So let’s take a look at what the markup looks like for a working Affix:

The markup above for obvious reasons doesn’t include all the content articles of the Veggie Lorem Ipsum section as shown in the demo and the whole Bacon Lorem Ipsum section is also not included.

First we take a look at the content part:

  • Each content section and each subsection requires its own ID. At least if you want the Affix to react to the scrolling of all of those (sub)sections of the content.
  • I have set the width of the content with the new Bootstrap 3 classes: the number of columns on larger screens and the number of columns on smaller screens. On smaller screens the width is set to all of the 12 columns. I did this because in my opinion using screen real estate on a smaller screen for an Affix is not a smart thing to do.
  • Not specifically required to get a working Affix is the use of an ID for the content. I have added this because it will allow me to do some CSS styling.

A second requirement is that you need to insert two data attributes in the opening body tag. Unfortunately this isn’t mentioned in the Affix section on the Bootstrap website. You need to look at the Scrollspy section there to find this:

  • The attribute data-spy="scroll" is necessary to ensure that the whole page is being spied upon to see if any scrolling behavior takes place. You need to attach this attribute to the body tag because there is only one scrollbar and that’s part of the window. If we’d use this attribute on the content section nothing would happen because the content section doesn’t have its own scrollbar (see above where I explained the difference between an Affix and a Scrollspy).
  • The data-target="#affix-nav" attribute will ensure that the scroll behavior caught/seen will then target the nav(igation). The value for this attribute (in our example: “affix-nav”) must be the same as the ID used for the nav element.

Now that these conditions are set in place we can focus on the Affix.

The Affix

The Affix (= the ul with the data-spy=”affix”) must sit inside another element. I have used a nav element for this. The opening nav tag holds an ID. This ID links the nav to the data-target attribute on the opening body tag. So it’s vital that these are the same.

I have also used responsive utilities to make the Affix only visible on larger screens.

We also need to add a class “nav” to the ul. And each (sub)ul also needs the class “nav”. Without this class the list items in that ul would simply be skipped when you scroll through the content (sub)sections.

For the Affix to work we also need to attach at least the attribute data-spy="affix". You will also often see the attribute data-offset-top and possibly data-offset-bottom attached to the opening ul tag. Understanding what these attributes do and how to set their values is important and also a bit complex.

The Affix plugin was created to change the “affix” class when someone scrolls the content. If you haven’t worked with jQuery or JavaScript before this might be a bit confusing. jQuery can add and/or remove a class from an element dynamically. You can write scripts that specify that when some event happens a class should be added (or removed). So the class is only applied to the element for a certain duration. Unlike CSS where a class attached to an element is there all the time (until you remove it from the code yourself).

While someone scrolls the content from top to bottom the class “affix” changes from “affix-top” to “affix” to “affix-bottom”. In this Firebug screenshot you can see that the ul has the class “affix-top” (this screenshot was taken when the scroll was at the top of the content section).

Firebug screenshot showing class affix top
The change from class “affix-top” to “affix” takes place at the distance from the top (of the window) that’s set by the attribute data-offset-top. And the same for the change from the class “affix” to “affix-bottom” with the attribute data-offset-bottom.

To really understand what this implies you need to know that the CSS positioning for the duration of the “affix-top” class is different from that during the “affix” phase of the scroll. And that the CSS positioning changes again when the class “affix-bottom” is applied by jQuery. During the “affix-top” phase the CSS position is set to static. When the “affix” class is active the position is set to fixed. And when the “affix-bottom” class kicks in it changes to absolute.

This tutorial isn’t meant to explain basic things about CSS positioning. For this I recommend the article by Noah Stokes CSS Positioning 101 on A list apart.

What is important here is that when someone scrolls the page elements (at the top of the page) will disappear from view. To prevent this happening to the affix-nav it needs a fixed CSS position. You can see this effect when you scroll the
DEMO 2

But as a consequence of this the ul element gets ‘ripped’ out of the flow of HTML elements. Being a fixed element (for that duration) it becomes independent of its parent element the nav.

In this example of what can go wrong I have changed the anchor text for the first sub item in the navigation to “Sweet Jelly that is really jummy and could be eaten all the time”. I have also set a different background-color for the “active” item. Scroll the example and you will see that part of the navigation overlaps the content (when the anchor text is long enough). As you continue scrolling to the Veggie section you’ll also see that the width of the nav changes.

Styling Affix classes

Where the Bootstrap website says “Requires independent styling” it really means that you must set style rules for each of the Affix class phases. Otherwise you’d get strange/funny effects (maybe that’s why they added the wink behind their warning).

In the demo I only included a content section and the affix-nav. This could be handled with only one phase “affix” and you don’t need the “affix-top” and “affix-bottom”.

My advice is to set the CSS positioning for each phase explicitly even though the “utilities.less” file and of course the Bootstrap style sheet already set the “affix” class to fixed. Better save than sorry. To see to entire CSS styling for the demo above click on the accordion below:

The Affix in the second demo behaves slightly different from that in the first demo: When a submenu is not active it is hidden. You can find the CSS rules for this in lines 44-49.

Affix on page with navbar

Web pages normally contain a header and a footer section. And that’s where a judicious setting of the offsets and smart styling of the “affix-top” and “affix-bottom” classes play an important role.

When a user starts scrolling a web page you often want the header section to disappear from view. You set a value for data-offset-top that is based on the outer height of the header section plus a little extra (e.g. 10px). That way the top of the page will first disappear from view and then – when the content section starts – the “affix” class is applied by the plugin and the affix-nav becomes fixed. The content section continues to disappear from view but the nav/ul stays visible in the window.

If the web page contains a footer (and/or other content you don’t want reflected in the Affix) you set the data-offset-bottom to a value corresponding to the outer height of the ‘footer’ and a bit extra. Without it the affix-nav would get confused when it should jump to the last item in its list. If you set the value for the data-offset-bottom larger than this the nav/ul might well disappear on you before your scroll reaches the end of the page (caused by the change to an absolute CSS positioning). You can of course also remedy this with CSS rules for that “affix-bottom” phase.

Also you’d better explicitly set the width of the ul during the “affix” phase. Remember that it’s not contained by its parent element anymore. To avoid unwanted changes in the width of the nav/ul I translated the grid class I used for the nav into pixels and applied that as a CSS rule to the ul.affix selector.
DEMO - Affix on page with Navbar, top section and footer

You can find the HTML and CSS code for that demo in the accordion below:

Affix with a script

Until now we have used the data attributes approach. We can also use a script. Before we add the script we need to replace the 2 data attributes on the ul element with the “affix” class by:

We can now add the script (below the link for jquery and for the Bootstrap js file):

I only added an offset for the top because I don’t need a bottom offset. If you need to set an offset for the bottom you can see an example on the Bootstrap website where a function is used that calculates an offset based on the ‘outer height’ (includes the border and padding) of the footer (in the example they use the class for the footer that they use on the website so you need to replace this with your own tag, id or class for the footer section). Being able to calculate the offsets is an advantage of using a script instead of a data attribute for the Affix.

18 comments

Walter - April 30, 2015 Reply

First of all, thanks for this awesome tutorial. I love your step by step tutorials. I’m using a fixed navigation menu (130 px height) at the top of my page and everything become a true nightmare when I try to mimic your demo. When I click on Affix, the content section jumps at the top of the page, leaving the content behind the fixed header. I’ve tried to use datta-offset on the body tag, but adding this break the Affix (all anchor tags). Adding some padding to the body tag, doesn’t work either. I am lost.
Any advice for fixing this issue? Thanks for you time and sorry for my English. Thank you very much!

Theo - May 2, 2015 Reply

Hi Walter,

Thanks.

Regarding the Affix with a fixed navbar: the Affix plugin always positions a content section/article at the top of the viewport. With a fixed navbar part of that content becomes hidden behind the navbar.

The data-offset attribute that you can set for the Affix plugin is used to position the nav element. As you noticed yourself you cannot used that attribute to position the content section.

An approach for having an Affix on a page with a fixed navbar can be found on the Bootstrap 2.3.2 website. Seeing that the developers of Bootstrap use this approach probably means that it’s difficult/impossible to do it another way. I am looking into a solution with JavaScript but for now my suggestion would be this:

Use a containing div for each article heading. This containing div is then styled with a top margin (of approximately the height of the navbar).
In the demo you’d insert a div tag before and after each h2 heading (with the href attribute) in the content section and give that div a class attribute. Then style that div with a top margin.

This will only work for those article heading and not for the (sub)sections inside the articles (of the demo). As you can see on the Bootstrap 2.3.2 website they circumvented this issue by not using sub list items in the affix nav.

Another thing to be aware of is that you actually create a gap between the content articles. If the margin-top has a value of approximately the height of the navbar this will not be noticed by viewers. But I would advise against adding more fixed elements at the top of the page (e.g. a Jumbotron). If you need other elements at the top of the page you’d better give those a static/default position).

Cheers, Theo

Dan Sullivan - November 7, 2014 Reply

This is very good.

Coen Coppens - October 1, 2014 Reply

Awesome tutorial, just what I needed!

FiqromJr - July 20, 2014 Reply

Thanks You! 🙂

James - June 4, 2014 Reply

Thank you!

The explanation on the bootstrap website is seriously confusing. It just leaves out most of the stuff you need to do!

Emilio - May 12, 2014 Reply

Theo,

As others have said, this article is a great help.

However I have to say this “floating navbar” concept is pretty troublesome. In Bootstrap’s own website (http://getbootstrap.com/getting-started/) it’s easy to get it to “break” and it stops following scrolls until you reload the page.

In your own example, you’ll note that clicking on links on the navbar actually scrolls too far; with the location of the ID hidden behind your top bar. I think that’s actually a problem in the javascript and users will have to put their id tags “early” in the text so it goes to the right place.

FYI your article ID’s don’t match what’s no the nav bar for Veggies and Bacon so we experience “weird” behavior there.

Overall it’s a neat user interface element but it is *a lot* of trouble to get working (mine always thinks it’s on the last section for some reason) and I’m not sure if it’s worth it when it’s so easy to break.

Theo - May 17, 2014 Reply

Thanks Emilio for the compliment. You’re not the only one who finds the Affix a frustrating task. Glad this tut was helpful.

Checked your remarks about my ID’s in the Veggies and Bacon Affix and the location of each section. These are all correct in the tut, so my guess is that it must have something to do with how you implemented them.

Cheers, Theo

Petter Söderlund - February 25, 2014 Reply

This is great. A reasonably short tutorial where you really focused on to get started with affix, really helped me. I noticed a flickering of the submenu appearing at the affix position when loading the demo with the navbar. This was a problem for me waiting for other js-stuff to load etc. Cheers!

Theo - February 25, 2014 Reply

Hi Petter,

thank you for making me aware of this glitch. I changed the tutorial to prevent this from happening. Had to do with the ‘affix’ class I used in the HTML. Actually it’s better to just let the affix plugin add the affix classes.

Pranalya - February 14, 2014 Reply

Excellent Explanation…Thanks a Lot……

Theo - February 14, 2014 Reply

Thanks Pranalya

Gabe Logan - February 8, 2014 Reply

Great explanation, thanks!

Bas Brands - January 21, 2014 Reply

Great post. This finally helped me get the affix working right. This should be on the Bootstrap website!

Theo - January 21, 2014 Reply

Thanks Bas!!

It’s great to read such a positive comment.

Paul Hill - January 13, 2014 Reply

This is the only sane explanation of affix I’ve been able to find. With help from your tutorial, I’ve actually got it working. 🙂

Theo - January 18, 2014 Reply

Thanks Paul. Glad it helped you out.

Alvaro Flores - November 10, 2013 Reply

Excellent explanation. Thank you very much!

Add your comment