htmlcsssticky

Position sticky does not remain centrally aligned upon screen resize


I've noticed that centrally aligning (vertically) an element with position sticky starts to break down after a certain reduction of screen width, depending on that elements width. Unlike position: fixed, the sticky element eventually gets stuck and loses its central alignment.

Any idea why this is happening and a workaround?

div {
  height: 100px;
  width: 500px;
  background-color: red;
  position: sticky;
  left: 50%;
  transform: translateX(-50%);
}
<div>
</div>


Solution

  • I came back to this question after some time. PIzmAR's answer is a good workaround but does not explain why this is happening as fy data suggested and there is a better solution without wrapping HTML elements.

    The reason this happens is actually simple. Sticky applies stickiness to both Y and X axes.

    Since it also keeps its position/size in the normal DOM flow, its untransformed state will (depending on the sticky elements width) eventually hit the far-right most size of the viewport and create overflow. At this point, sticky elements just behave in the same way they do on the Y axis and STICK in position. That's it! It's the left: 50% attribute that's the culprit, in the same way the top: attribute works when using sticky in a Y direction.

    This isn't what I needed at the time and I doubt it's what most people need when using left to centrally align a sticky element. So the best solution is to treat the sticky element as relative and use margins to center it on the X axis. Like so:

    #box-2 {
      top:0;
      left: 0;
      transform: translate(0);
      margin-left: auto;
      margin-right: auto;
    }
    

    Run the code snippet in fullscreen to see some examples:

            function updateBoxClasses(boxId, highlighted) {
                const box = document.getElementById(boxId);
                if (highlighted) {
                    box.classList.add('highlighted');
                    box.classList.remove('box');
                } else {
                    box.classList.add('box');
                    box.classList.remove('highlighted');
                }
            }
    
            function updateClasses() {
                const viewportWidth = window.innerWidth;
                if (viewportWidth <= 1000) {
                    updateBoxClasses('box-ghost', true);
                    updateBoxClasses('box', true);
                    updateBoxClasses('box-2', true);
                } else {
                    updateBoxClasses('box-ghost', false);
                    updateBoxClasses('box', false);
                    updateBoxClasses('box-2', false);
                }
            }
    
            updateClasses();
            window.addEventListener('resize', updateClasses);
    * {
      box-sizing: border-box;
    }
    
    body {
      width: 100%;
      padding: 0;
      margin: 0;
    }
    
    div#container {
      height: 2000px;
      width: 100%;
      position: relative;
    }
    
    div#heading {
      display: flex;
      justify-content: center;
      align-items: center;
      font-size: 64px;
      height: 200px;
      border: solid black 2px;
    }
    
    #box, #box-2 {
      position: sticky;
      left: 50%;
      transform: translateX(-50%);
      display: flex;
      justify-content: center;
      align-items: center;
      font-size: 32px;
      color: white;
      opacity: 50%;
      top: 100px;
    }
    
    #box-2 {
      top: 210px;
      margin-top: 10px;
      left: 0;
      transform: translate(0);
      margin-left: auto;
      margin-right: auto;
    }
    
    
    
    span {
      font-size: 12px;
      position: absolute;
      top: 5px;
      left: 10px;
    }
    
    #box-ghost {
      position: absolute;
      top: 0;
      left: 50%;
      background-color: transparent;
      border-style: dashed;
      border-width: 10px;
      opacity: 70%;
      display: flex;
      justify-content: center;
      align-items: center;
      font-size: 32px;
    }
    
    /* div#half-width {
      position: fixed;
      left: 0;
      right: 0;
      width: 250px;
      height: 100px;
      background-color: grey;
      display: block;
      color: lightgray;
      display: flex;
      justify-content: center;
      align-items: center;
      font-size: 32px;
    } */
    
    .highlighted {
      border-color: blue;
      color: blue;
      background-color: blue;
       width: 500px;
      height: 100px;
    }
    
    .box {
      color: red;
      background-color: red;
      border-color: red;
       width: 500px;
      height: 100px;
    }
    <div id="heading"><span>position: relative</span>Resize viewport to see demo</div>
    <div id="container">
      <div class="box" id="box"><span>position: sticky (left: 50%, transform: translateX(-50%))</span>500px</div>
      <div class="box-ghost" id="box-ghost"><span>position: absolute (left: 50%)</span>500px (not transformed)</div>
      <div class="box" id="box-2"><span>position: sticky (margin-left: auto, margin-right: auto)</span>500px (solution)</div>
    </div>