586Days

586 days since this post was last revised. Specific details are likely out of date.

In WebKit devices (older Android and iOS) position: sticky has been around for some time. It was subsequently removed from Android/Chrome (at v35). Now, it’s back in the standards and back in Chrome from version 56 onwards.

Sadly there is significant disparity in how Chrome/Firefox and Safari now implement the feature.

Here is how the feature is described in the specification: https://www.w3.org/TR/css-position–3/#sticky-pos

A stickily positioned box is positioned similarly to a relatively positioned box, but the offset is computed with reference to the nearest ancestor with a scrolling box, or the viewport if no ancestor has a scrolling box.

As a simple example, consider a blog post where all headers are sticky and stick to the top of the page as the user scrolls.

See the Pen Simple position: sticky Example by Ben Frain (@benfrain) on CodePen.

The requisite code is remarkably small. Just tell each header to be sticky and tell it where you want it to stick:

.to-Stick {
    position: sticky;
    top: 0;
}

OK, great. However, implementations in Chrome/Firefox and Safari differ when transforms are applied to parent elements (note, the latest Chrome implementation is only in dev/Canary as I write this).

You’ll need to view the following example in both iOS/Safari and Chrome/Firefox to see the difference (for now you can use Chrome Dev or Canary, enter responsive mode in the tools and selected something like the Nexus 5X as the device).

Here is the amended example:

See the Pen Simple position: sticky Example by Ben Frain (@benfrain) on CodePen.

To explain the example, the body element has a transform: translateY(-44px) transform applied. Safari shows the sticky box sticking to the top of the viewport while Chrome has the sticky element ‘sticking’ 44px sooner.

Whether this disparity is due to the specification making no allowance as to what should happen when transforms are being applied to the scrolling box I cannot say. At first glance, WebKit seems to be doing it right. However, in terms of adherence to the specification, things are not so clear.

Chrome/Firefox is factoring the transform into the calculation of where the sticky element should show (although they don’t factor in a top: -44px on the body which I thought was weird). Specification wise, Chrome is probably closer — the sticky element is positioned with reference to its nearest ancestor (the body in this example, which has been transformed to a different visual position). However, practically, I think the WebKit implementation makes total sense too. If you add top: 0 to a sticky element, no matter what’s happened with the container, the element in question is going to stick to the top of the viewport.

There is at least one bug report (https://bugs.chromium.org/p/chromium/issues/detail?id=674356) in relation to the Chrome implementation but I can’t decide if it only seems like a bug because it differs to Safari.

Ultimately, as developers we could deal with either approach. What is not cool is a different implementation across browsers.

So, enjoy the fact you can start using position: sticky again, but be aware that if you have a transform on the scrolling box it belongs to, things could start to get messy.

Ben Frain Developer, Author: 'Enduring CSS', 'Responsive Web Design with HTML5 & CSS3'.