17

I am trying to keep an element from scrolling past left: 0 using position: sticky. This works fine in some cases, but I have noticed that if the element width increases it stops working. For example, the following works:

#header {
  position: sticky;
  left: 0;
  width: 50%;
  background-color: #888;
}
#page {
  height: 80vh;
  width: 120vw;
  background-color: #000;
}
<div>
  <div id="header">
    Where is my mind?
  </div>
  <div id="page">
  </div>
</div>

But if I increase the witdth of header element to 100% it stops working.

#header {
  position: sticky;
  left: 0;
  width: 100%;
  background-color: #888;
}
#page {
  height: 80vh;
  width: 120vw;
  background-color: #000;
}
<div>
  <div id="header">
    Where is my mind?
  </div>
  <div id="page">
  </div>
</div>

Why does this happen? And is there any way to use position: sticky to prevent the header element from scrolling when it's width is 100%? I prefer not to use position: fixed in this case.

jdnz
  • 932
  • 1
  • 7
  • 18

2 Answers2

37

I now understand what is happening. The issue is the different way the browser treats the width and height of a <div>. The default values of auto mean that the width of the <div> is 100% while the height is set by the content. If the content is wider than 100%, then on horizontal scroll the sticky element hits the end of the container <div> and, since it cannot leave the confines of the container, begins to scroll. This doesn't happen in the same situation for vertical scrolling since the container <div> is as tall as the content by default.

To prevent this happening, we have to ensure that the container <div> is as wide as its content. This can be done in most browsers (not Edge or Explorer) by including width: max-content in the container style. Alternatively, as proposed in mfluehr's answer, putting overflow: auto creates a new block formatting context that is as wide as the content. Another option is to use display: inline-block or inline-flex etc. to cause the container <div> to base its width on the content.

For example, using two of these techniques, you can create headers, sidebars and footers that stick for a page that can scroll vertically and horizontally:

body {
  padding: 0;
  margin: 0;
}
#app {
  overflow: auto;
  height: 100vh;
}
#header {
  background: blue;
  width: 100%;
  height: 40px;
  position: sticky;
  top: 0;
  left: 0;
  z-index: 10;
  color: white;
}
#sidebar {
  position: sticky;
  background: green;
  width: 200px;
  height: calc(100vh - 40px);
  top: 40px;
  left: 0;
  color: white;
  flex-grow: 0;
  flex-shrink: 0;
}
#container {
  display: inline-flex;
}
#content {
  background: #555;
  height: 200vh;
  width: 200vw;
  background: linear-gradient(135deg, #cc2, #a37);
  flex-grow: 0;
  flex-shrink: 0;
}
#footer {
  background: #000;
  height: 100px;
  z-index: 100;
  left: 0;
  position: sticky;
  color: white;
}
<div id="app">
  <div id="header" ref="header">
    Header content
  </div>
  <div id="container">
    <div id="sidebar" ref="sidebar">
      Sidebar content
    </div>
    <div id="content" ref="content">
      Page content
    </div> 
  </div>
  <div id="footer" ref="footer">
    Footer content
  </div>  
</div>
jdnz
  • 932
  • 1
  • 7
  • 18
  • 3
    Man, this is a great answer. It really helps to illustrate how the browser is calculating width and height differently. I couldn't figure out why my width wasn't expanding but the height was. This definitely puts it into perspective. Thank you! – Xenostar Sep 22 '21 at 19:08
8

This is an interesting problem. I don't know why, but putting overflow: auto on the container around the <div>s seems to fix the issue.

You can add height: 100vh to the container to let the content inside overflow with scrollbars.

body {
  margin: 0;
}

#container {
  overflow: auto;
  height: 100vh;
}

#header {
  position: sticky;
  left: 0;
  width: 100%;
  background-color: #888;
}
#page {
  height: 200vh;
  width: 120vw;
  background: linear-gradient(135deg, #cc2, #a37);
}
<body>
  <div id="container">
    <div id="header">
      This is the header.
    </div>
    <div id="page">
      Page content goes here.
    </div>
  </div>
</body>
mfluehr
  • 2,832
  • 2
  • 23
  • 31
  • Interesting and bizarre, since putting `overflow: auto` seems to kill stickyness in the vertical direction. I would like it to work with body horizontal scroll however rather than the div having its own scroll bar (which might appear off screen if the div is taller than the viewport). – jdnz Aug 10 '19 at 09:50
  • 1
    If you add `height: 100vh` to your container, I think the overflow scrolling works as you want. See my edited answer. – mfluehr Aug 12 '19 at 13:19
  • Nice, and with this addition putting `top: 0` on the header also keeps it fixed in the vertical direction. I am a bit confused about why this is the case though. – jdnz Aug 12 '19 at 18:56