That time when CSS’ position:fixed didn’t

There’s been an ongoing bug with my blog after I added the “hamburger” menu/options on the left side. In essence, adding it interfered with another “feature” of the individual blog post pages where the title of the post sticks to the top of the browser window as you scroll down through the text. And, yes, you guessed it, both features are provided by JavaScript libraries, different ones, by different people. It’s this week’s edition of JavaScript Libraries Gone Wild!

Let me describe what was happening. The slide-in panel menu always worked perfectly. There were no issues there at all: click on the hamburger, the side panel slides in from the left. Click on it again (or anywhere in the main page), it slides back to become hidden. Brilliant. Just what I wanted. The way the sticky title was supposed to work is that you scroll down through the blog post and when the title reached the top of the browser window, it sticks there and the remaining content just slides underneath as you scroll. If you scroll up again, when the content reaches the point where the title is designed to be, it unsticks and is shown in its designed position in the page. Supposed to. What was actually happening was that, when you got to the point where the title should stick, it simply disappeared. Scroll up again and where it would come unstuck, it magically reappeared. What the…? Time to look at my JavaScript and that of the two libraries.

To be honest, at this point I was reminded of @iamdevloper’s recent tweet:

Maybe I should just discard both libraries and write my own code that does both things. Said every CTO ever, then instructing his web team to write it.

The problem with my investigation was I naturally assumed it was a JavaScript problem. I had two separate libraries, jPanelMenu and Sticky, both of which worked just fine on their own, but somehow, together, the sticky blog post title was anything but. When I first noticed this effect, I wrote some JavaScript to turn off the panel menu once the page had scrolled enough that the hamburger wasn’t visible any more. If it’s not visible, you can’t click on it, right? Since the blog post title always appeared below the hamburger, this workaround worked pretty well.

// ...
makeStickyTitle = function() {
  $("#postTitleContainer").sticky({ topSpacing: 0 });

  $(window).on("scroll", function () {
    var scrollTop = $(window).scrollTop();

    if (panelMenuActive) {
      if (scrollTop > hamburgerHeight) {
        panelMenu.off();
        panelMenuActive = false;
      }
    }
    else {
      if (scrollTop <= hamburgerHeight) {
        panelMenu.on();
        panelMenuActive = true;
      }
    }
  });
},
// ...

Well, not completely well. You see turning the panel on or off caused a stutter in the scrolling as the styles for various elements in the DOM were updated and handlers were removed/added and elements were manipulated in various weird ways. You could grab the scrollbar thumb with the mouse and drag it down towards the bottom of the page: it would stop after the panel was disabled or enabled, necessitating that it be grabbed again to complete the action. The sticky title though worked perfectly.

Time for some debugging action. First I used, as usual, FireBug. It’s the debugger I’ve used over the years and like it a lot. I turned off my hack scroll event handler, and was able to show that the Sticky code was doing the right thing at the right time by adding the “position: fixed;” style to the title element. However, although the DOM didn’t show anything untoward style-wise, the title just disappeared. When I turned on the scroll event handler and followed through in the debugger, exactly the same style changes produced a visible title element stuck to the top of the window. It was most frustrating.

I then switched to Chrome’s debugger. Maybe it’d show a bit more information or some different information about what was going on. And blow me down but it did. You see, if you click on an element in the HTML view, it shows a popup over the element in the preview pane. If the element is not visible there, it also adds a little triangle to that popup to point to where the element is if only you’d scroll that far up or down. And, the “invisible” sticky title wasn’t invisible, it was just positioned at the top of the page, not the window. The “position: fixed;” style wasn’t working properly for some unfathomable reason.

Now it was time for Google and some searching.

It turns out that it’s a known problem with “position: fixed;” and, get this, the “transform” style. Wait, what? Where did that come in? Yes, you guessed it: the panel menu code was setting a “transform” style on the main page when it was activated (so it could transform the entire page by shifting it right to show the side panel). It turns out that once “position: fixed;” gets used on an element inside a transformed container, the element’s position becomes relative to the container, not the browser window. Eric Meyer has the best explanation for the effect, from four years ago, natch. To quote:

…a transformed element creates a containing block even for descendants that have been set to position: fixed.  In other words, the containing block for a fixed-position descendant of a transformed element is the transformed element, not the viewport.

And that was my problem in a nutshell. The title is an h2 element with a fixed position whose parent’s parent’s … parent is a div with a transform style. Boom! It becomes fixed to the container and not the browser window. Since the top of the container is the top of the page which isn’t visible at that moment, the title just … disappears. And the transform? An in-place shift, essentially; a transform to exactly the same place. It wasn’t doing anything visibly in the non-activated state.

At that point, my hack was made easier. Just remove the style when the hamburger wasn’t visible, and add it back again when it was.

// ...
makeStickyTitle = function() {
  $("#postTitleContainer").sticky({ topSpacing: 0 });

  $(window).on("scroll", function () {
    var scrollTop = $(window).scrollTop();

    if (panelMenuActive) {
      if (scrollTop > hamburgerHeight) {
        var panel = $(".jPanelMenu-panel");
        panelTransform = panel.css("transform");
        panel.css("transform", "");
        panelMenuActive = false;
      }
    }
    else {
      if (scrollTop <= hamburgerHeight) {
        $(".jPanelMenu-panel").css("transform", panelTransform);
        panelMenuActive = true;
      }
    }
  });
},
// ...

Since it was the actions of (de)activating the panel that were causing the scroll hesitation, this code, although still a bit of a hack, does not have the same problem. That’ll do for now, since the next step will be to write my own sliding panel code.

Romney Bay House

Album cover for Eccentrix RemixesNow playing:
Yello - Rubberbandman [Rubber Mix]
(from Eccentrix Remixes)



Loading similar posts...   Loading links to posts on similar topics...

No Responses

Feel free to add a comment...

Leave a response

Note: some MarkDown is allowed, but HTML is not. Expand to show what's available.

  •  Emphasize with italics: surround word with underscores _emphasis_
  •  Emphasize strongly: surround word with double-asterisks **strong**
  •  Link: surround text with square brackets, url with parentheses [text](url)
  •  Inline code: surround text with backticks `IEnumerable`
  •  Unordered list: start each line with an asterisk, space * an item
  •  Ordered list: start each line with a digit, period, space 1. an item
  •  Insert code block: start each line with four spaces
  •  Insert blockquote: start each line with right-angle-bracket, space > Now is the time...
Preview of response