For a time “dynamic fixed” elements were the hot web design feature: scroll a site and everything moved as expected, but when a particular element (often a menu bar, sometimes an advertisement) reached the top of the page it would fix itself in place, while the rest of the document continued to scroll underneath it. Scroll back, and the element would reattach itself to the document.
This behaviour - a dynamic hybrid between the standard position: static
and position: fixed
- was often added to a page with JQuery, via one of a hundred different plugins. Like many popular features, it was eventually adopted into an official specification, meaning that the effect could be achieved with native CSS, with no frameworks, scripts, or other dependencies… but in this case, the adoption process was (and continues to be) somewhat troubled.
How Things Were Meant to Be
The behavior was codified as a new CSS value: position: sticky
. This, combined with a clever use of top
(in the context of sticky
, the distance from the top of the body
at which the element will “stick” when scrolled; alternatives are left
, bottom
and right
for scrolling in those directions) was intended to cover the range of popular use cases. The syntax, then, should be pretty simple:
#stickytest {
position: sticky;
top: 0px;
}
Applied to something like an image, the markup would be:
<img src="geckofoot.jpg" alt id="stickytest">
<p>Lorem ipsum…
If you test that code on a page with appropriate markup and content in a recent version of Firefox or Chrome 56+, it will work just fine. Moving to other browsers reveals “sticky’s” awkward position:
- Safari 6.1+ (on both desktop and mobile) support
sticky
with a vendor prefix. Somewhat unusually, the prefix is applied to the value, not the property, since it’s the former that’s new. - There’s an added catch: in Safari
sticky
only works with elements that aredisplay: block
, so the CSS for this sticky image example becomes:
#stickytest {
display: block;
position: -webkit-sticky;
position: sticky;
top: 0px;
}
In addition:
- Placing the sticky element inside a parent that has
overflow: hidden
applied to it will cause the sticky behaviour to fail. - Officially,
sticky
should also work withdisplay: table
, including table cells, as it’s very useful for navigating long rows of table data while keeping column headers in view. Unfortunately, browser implementation of this feature has been lacking so far.
Chrome Travails
You’ll probably find web pages that insist Chrome has support for position: sticky
as an experimental option, and that was true… until the option was removed entirely in Chrome 37. The Google development team felt that keeping position: sticky
was too much of a challenge in their quest to improve composition and rendering speed in the browser, which was achieved in Chrome 56, when position: sticky
was returned to support in the browser.
Fortunately, there are a number of options for this: the Filament Group has a JQuery solution, along with many others; my personal favorite is Oleg Korsunsky’s stickyfill, which has the option of working with or without JQuery.
The example on the StickyFill page adds the sticky behaviour as a class, but it’s more likely to affect only one element, making an id
a more logical selector. In the case of this demo, after inserting the script at the bottom of the page, I’d add:
Stickyfill.add(document.getElementById('stickytest'));
Note that top
, left
, bottom
and right
for the element is measured from the body
. This will include any margin already in place on the body
element.
Keep It Simple
As with all new features, position: sticky
has the potential for misuse. To avoid UI disasters, I would recommend the following:
- In theory,
position: sticky
can be used to fix elements inside any scrolling container, as shown in the example at the top of this page. Please don’t use sticky more than once per page: we don’t need multiple content boxes acting like clogged drains. (In addition, all the polyfills available forposition: fixed
fail in providing this feature; the demo for this page only works because it is inside aniframe
). - Be very careful using
position: sticky
at mobile screen sizes: anything that is stuck immediately takes up screen real estate, reducing the room for your content. Make sure you’re making an elementsticky
because it’s required or extremely helpful to do so, not because it “looks cool”. Remember that it’s easy for users to flick-scroll to the top or bottom of most pages; don’t get in the way of that behaviour.
To provide a reasonable use of sticky
elements, write a @media
query that counters position: sticky
at smaller screen sizes:
@media all and (max-width: 600px) {
#stickytest {
position: static !important;
}
}
This returns the element to normal flow in the document if the viewport is 600 pixels wide or less. You’ll need to write a similar rule in JavaScript with matchMedia
if you’re using a polyfill. Re-writing and adding to the script above:
var sticky = document.getElementById('stickytest');
var screencheck = window.matchMedia("(max-width: 600px)");
Stickyfill.add(sticky);
if (screencheck.matches) {
Stickyfill.remove(sticky);
}
- As a rule, “stuck” elements should be the new cutting-off point for content. Don’t “stick” an element in the middle of a block, as having scrolling text appear both above and below a fixed element is very distracting, and makes the text difficult to read.
- Similarly, try to avoid sticking elements that cut off part of a text block, forcing the user to scroll in a decreased amount of space to read a full line.
- Ensure that the fixed element does not occupy more than the minimum expected height of the viewport. If it is taller than its container when it is “stuck”, users will never be able to see all of its content; nor are they likely to be able to read any of the other content on the page.
- Further to this, you might want to indicate that content is truly disappearing under the stuck element: a partial transparency effect when the element is in it’s “stuck” position, such as my “frosted glass” effect, can provide a useful visual cue.
In principle, as series of elements could be provided with the same position: sticky
value, effectively replacing each other at the chosen point on the screen as the page scrolls. While this can be effective, it’s also potentially very confusing to users, and should be employed carefully.
Unfortunately there’s no “stuck” event in JavaScript yet to report when an element has entered its fixed position; right now, you have to test for that independently. (Some polyfills add a class
to stuck elements to make up for this lack).
Enjoy this piece? I invite you to follow me at twitter.com/dudleystorey to learn more.
Check out the CodePen demo for this article at https://codepen.io/dudleystorey/pen/yNxPRy