MTJ Hax

Sticky Footers Break Bootstrap ScrollSpy

Posted in bootstrap, code, jquery by mtjhax on February 12, 2013

If you saw my previous post, you know that I am currently playing with the Twitter Bootstrap ScrollSpy plugin. I reviewed their sources (which took a bit of effort as the code was written for compactness, not readability) and got my examples working. When I tried it with a real site, however, everything stopped working. ScrollSpy would only highlight the last element in my nav.

I fired up the debugger on an un-minified bootstrap.js to look at the size and position variables and saw that the value for the maxScroll var was 0. maxScroll holds the difference between the total scrollable height and the actual/visible height (as calculated by jQuery height()). This value should only be 0 if the scrollable content fits entirely in the visible area, yet my content was 1400px in height and the visible height was only around 400.

DOM inspection revealed that the document body had the correct scrollHeight value of 1400, but $('body').height() was 1400 as well. Normally, $('body').height() would return 400, the visible size. I had one of those “what the actual f…” moments for a few seconds.

Light Dawns

The site layout I am using is employing the sticky footer technique, which forces your footer to the bottom of the visible browser window, even when the page is smaller than the window. The relevant CSS is as follows:

* {
  margin: 0;
}
html, body {
  height: 100%;
}
.wrapper {
  min-height: 100%;
  height: auto !important;
  height: 100%;
  margin: 0 auto -142px; /* the bottom margin is the negative value of the footer's height */
}
.footer, .push {
  height: 142px; /* .push must be the same height as .footer */
}

/*
Sticky Footer by Ryan Fait
http://ryanfait.com/
*/

Oops. We are explicitly setting the height of body to 100% of the document height, so $('body').height() is no longer just the visible area, but the same as document.body.scrollHeight.

What’s the solution?

It turns out that someone already tried to get a workaround merged into Bootstrap, but they didn’t follow Bootstrap’s procedures for submitting a pull request and were summarily rejected. I’m not sure that fix would work anyway — it uses the sticky footer #wrap div to get a different scrollHeight, but the scrollHeight is not the problem — it’s $scrollElement.height() that is returning the full document height instead of the visible height.

Some options for a fix include:

  • Override the sticky footer styles for the page in question (just use html,body { height: auto }) or use a different stylesheet with the ScrollSpy page. This doesn’t feel right to me. Page-specific conditional styles are the path to the dark side.
  • Skip the sticky footer styles based on some clever CSS that conditionally removes them. This is still a conditional but it seems a bit more expressive and makes the solution all CSS. Imagine an HTML element like <div class="container unstick_footer">. The only problem is that this won’t be possible until CSS4.
  • Stop using sticky footers? Maybe you don’t really need them. A lot of people just add them to every project before they even have a design.
  • Put your scrollable content inside an element that has a fixed height and use ScrollSpy on that instead of the body. This would not work for everyone and makes it a bit more difficult for your scrollable area to adjust to different page heights. You would need some JavaScript and/or Responsive CSS styles to avoid just having a fixed-height box.
  • Submit a patch for Bootstrap that gets the visible height of the body even if some nutcase has defined it to have height 100% of the document height.
  • My favorite (which may not be possible) is to fix the sticky footers CSS so they don’t increase the height of the body beyond the visible window. I may need to call in a CSS ringer to help me with that one.

I’d love to explore all these approaches and compare their pros and cons, but time isn’t my friend. I’d love to hear about your ideas and any approaches that work.

Advertisements