htmlcsscss-positionstickyhorizontal-scrolling

Position sticky not working for horizontal scrolling when element width increases


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.


Solution

  • 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>