Scroll-Then-Fix Content

Avatar of Chris Coyier
Chris Coyier on

A reader sent me in a GIF showing off a cool effect they saw on Google on mobile. (Presumably the homepage you see when you launch Chrome on Android?) There is a search input in the middle of the page that scrolls with the page, but as it’s about to scroll off the page, it becomes affixed to the header. Let’s cover that, because, you know…

It’s a cool effect particularly if used to make UX better and not to affix some dumb intrusive ad. Here’s the GIF I based the idea on. Little choppy, but the idea is there:

Two States

Like most good tricks, there isn’t much to it. All we do is think of (and design for) the two different possible states:

  1. Search bar in its scrollable position
  2. Search bar in its fixed header position

We toggle between them simply by changing a class name. There is no trickery with having two search forms that reveal themselves in different scenarios. That’s good, as we don’t want to smurf around with keeping those in sync. Much easier to just move a single one around.

State One

(I’m going to use SCSS here because the nesting is nice for managing states.)

.top-header {
  position: fixed;
  top: 0;
  left: 0;
  width: 320px;
  height: 60px;
}

.search { /* Container just in case we want more than just the search input to come along */
  position: absolute;
  top: 155px;
  left: 20px;
  right: 20px;
  input {
    width: 265px;
    transition: width 0.2s;
    -webkit-appearance: none; /* Autoprefixer doesn't catch this */
  }
}

.top {
  height: 250px; /* Space in here for search */
  padding-top: 40px;
  position: relative;
}

State Two

Assuming we’ve put a class of “fix-search” on a parent element.

.top-header {
  ...
  .fix-search & {
    background: #eee;
  }
}

.search { /* Container just in case we want more than just the search input to come along */
  ...
  .fix-search & {
    position: fixed;
    top: 10px;
    input {
      width: 250px;
    }
  }
}

Switching States

The trick here is applying that class at just the right moment. In our little demo, we can just test for when that perfect moment would be and hard code that into some JavaScript watching for scrolling. jQuery style:

var wrap = $("#wrap");

wrap.on("scroll", function(e) {
    
  if (this.scrollTop > 147) {
    wrap.addClass("fix-search");
  } else {
    wrap.removeClass("fix-search");
  }
  
});

That’s all it takes to switch between the two states we’ve set up. If the page has scrolled down 147 pixels or more, it will have that class applied. If not, it doesn’t. Even if you go down and come back up the class will go away (because this little function gets called on every scroll event).

Demo

See the Pen Search Box in Content Moves to Fixed Header by Chris Coyier (@chriscoyier) on CodePen.

Debouncing

In the grand tradition of mentioning scroll debouncing whenever any demo binds an event to a scroll event: you should consider debouncing when binding functions to scroll events, because if you don’t, it’ll get called a zillion times and could be slow.

CSS

This is the kind of thing that would be sweet to do in CSS alone. No great solutions pop to mind just yet, but I’m continually amazed by crazy things people use CSS to do, so if something comes along I’ll update this.

Perhaps someday we’ll be able to do scroll position media queries?

Fixed Position Support

Note that this demo relies on fixed positioning, which has a sketchy history on mobile. While I’m tempted to say it has “pretty good” support these days, you should make the judgement yourself. Some reading:

This is just one (not particularly reusable) example

There are a lot of magic numbers in this demo. Anytime you are setting heights there should be some warning flags happening up in your brain. It doesn’t mean never do it, it just means be warned. In this demo, if that centered image in the header changed height, it would look weird pretty much no matter what. This isn’t the most flexible and forgiving of layouts as it is. Even if you fixed the header to look right after a change, the JavaScript has corresponding magic numbers for when to change state.

Perhaps some version of using waypoints (or the concept of it) could make a more bulletproof system.