Bootstrap: better Affix…

A useful little trick...

I love Twitter’s Bootstrap framework. I don’t think I say that enough. It makes structuring beautiful websites extremely easy and quick. Their JavaScript framework makes it so much simpler to implement most commonly used animations. It does so by adding a few jQuery plugins that can be invoked by making additions to the markup or with some minimal scripting.

One such plugin is Affix that comes in the standard compiled bootstrap.js file or as affix.js if you prefer compiling them yourself. It allows dynamically changing the position of an element based on the scroll position. All you have to do include the attributes data-spy="affix" and data-offset-top="xx" and optionally data-offset-bottom="yy" where xx and yy are numerical values in pixels of your top and bottom offsets. It works by comparing how far you’ve scrolled the document.

At first, it assigns the class .affix-top to your element. If you’ve scrolled more pixels than the top offset, that will replace it with the class .affix. If the distance between your element and the bottom of the viewport and your element gets larger than the bottom offset, it will be assigned the class .affix-bottom. At any point in time, an element will only have one of those three classes. Bootstrap CSS includes the position: absolute; attribute for the .affix.

The rest of the CSS styling will have to be done by you. By writing your own styles for .affix, .affix-top and .affix-bottom, you can obtain whatever effect you like. But there’s a problem. These classes are applied based on the page scroll position and not the position of the element with respect to the viewport (your browser’s page display area). I’m here to fix that for you.

What we’ll be doing

In this project, we will be modifying affix.js to work with an element’s position with respect to the viewport rather than the page scroll position.

What you’ll need

  1. affix.js obviously

    You can do it in either of the following ways.

    • From the source files

      Download the source files by clicking on the “Download source” button on this page. Unzip the file. You’ll find affix.js in the /js/ folder.

    • From the compiled bootstrap.js file

      If you’ve downloaded Bootstrap using the “Download Bootstrap” button, the affix plugin is contained in this file located in the /js/ directory. You’ll have to extract the very final jQuery plugin function from that one and place in a separate .js file.

  2. A good code editor

    Those with the ability of syntax highlighting and case sensitive ‘find and replace’ are preferable.

Let’s start

You’ll have to do the following.

Optional: Replace instances of ‘affix’

If your implementation does involve the use of affix.js as is and you wish to continue using that, you’ll have to make name changes to all the classes, functions and namespaces. Choose a name that could replace the word ‘affix’, preferably those that sound good when suffixed with ‘-ed’ and ‘-er’. I chose the word ‘stick’. You can name them whatever you like, but try and avoid the words that are already present in the script especially ‘position’.

Now begin with the case sensitive replacing. Replace all instances of ‘affix’ with ‘stick’, ‘Affix’ with ‘Stick’, and ‘AFFIX’ with ‘STICK’. You’ll save quite a bit of time in a good code editor.

Important replacements

Look out for Affix.prototype.getState or Stick.prototype.getState.

In that function, you’ll find a line that reads…

if (offsetTop != null && this.sticked == 'top') return scrollTop < offsetTop ? 'top' : false

Replace that part offsetTop with position.top – offsetTop like the following…

if (offsetTop != null && this.sticked == 'top') return scrollTop < position.top - offsetTop ? 'top' : false

You’ll find another line that reads this…

if (offsetTop != null && scrollTop <= offsetTop) return 'top'

Like before, replace the marked part with this…

if (offsetTop != null && scrollTop <= position.top - offsetTop) return 'top'

As it turns out, the script behaves desirably when it comes to bottom offsetting.

And that’s it for the JavaScript end of things.

Implementation

Applying this modified script is not particularly straightforward. You should not assign CSS position and placement attributes to the .stick, .stick-top and .stick-bottom itself. Rather you must assign them to the descendants or immediately succeeding sibling element. Here’s an example.

Here are two anchor elements styled like buttons, the first of which (Hello) is assigned with our modified data-spy="stick" attribute with a top offset of 100. When the button is 100px or less away from the top edge of the viewport, it will be assigned the .stick. I’ve assigned them attributes such that the button ‘Hello’ and the one after it, both, change their appearances while the second button ‘World’ is given a fixed position on the top right of the viewport.

Here’s the code used in those buttons.

<a class="btn btn-default btn-lg first-anchor" data-spy="stick" data-offset-top="100" href="">Hello</a>
<a class="btn btn-default btn-lg second-anchor" href="">World</a>

Here are the styles used.

<style>
  a.first-anchor.stick{
    color: #FFF;
    background-color: #000
  }
  a.first-anchor.stick + a.second-anchor{
    position: fixed;
    top: 51px;
    right: 0;
    color: #FFF;
    background-color: #000
  }
/* The navbar hides itself in landscape viewports with width less than 768 pixels, thus the following style */
  @media (max-width:767px) and (orientation:landscape){
    a.first-anchor.stick + a.second-anchor{
      top: 0
    }
  }
</style>

You can easily track an extremely thin element that moves relative to the rest of the page to assign styles to it’s descendant or sibling elements. The possibilities are endless.

That’s it!

Let me know if you found this useful or at least a bit interesting, sound off in the comments below. Share this piece to spread the word. If you’d like me to help you out with anything, let me know that too.

Thanks for reading…