Search code examples
javascripthtmlcss

scrollIntoView miss-understood behavior with sticky element


I've tried everything in ScrollIntoView() causing the whole page to move and Using scrollIntoView with a fixed position header, but I still end up with a weird behavior when using scrollIntoView.

I basically have a scrollable container with a sticky action button at the bottom of it. Within that container I want to use scrollIntoView on children elements. The function works well but when I scroll back up to the first item, it is not fully visible anymore and the parent container feels like it moved.

From many tests, HTML restructuring etc. I'm now quite sure that the issue comes from the sticky action container height, the scrollable container seems to be shifted by that height.

Any insights about that behavior? Is the HTML structure wrong for the use case?

Minimal reproduction: (codepen if you wanna play with it)

const scrollButton = document.querySelector('button');
scrollButton.onclick = () => {
  const item = document.getElementById('3');
  item.scrollIntoView({behavior:'smooth', block:'center'});
}
.main-container {
  flex: 1 1 auto;
  overflow: hidden;
  height: 300px;
  width: 50%;
  background-color: lightpink;
  padding: 16px;
}

.scrollable-container {
  overflow-y: auto;
  height: 100%;
  width: 100%;
}

.item {
  min-height: 200px;
  background-color: lightblue;
  margin-bottom: 8px;
}

.stick-action {
  padding: 48px 0px 12px 0px;
  pointer-events: none;
  z-index: 1;
  bottom: 0;
  position: sticky;
  text-align: center;
  background: linear-gradient(
    180deg,
    rgba(255, 255, 255, 0) 19.3%,
    rgba(255, 255, 255, 0.981337) 77.3%
  );
}
<button>scroll 3</button>

<div class="main-container">
  <div class="scrollable-container">
    <div id="1" class="item">First item</div>
    <div id="2" class="item">Second item</div>
    <div id="3" class="item">Third item</div>  
  </div>
  <div class="stick-action">action<div>
</div>


Solution

    • position: sticky needs to have siblings in order to work properly. In the OP, there is only one sibling to the sticky element which is .scrollable-container. Move the sticky element to .scrollable-container so it can properly function with the 3 .items.

    • It looks like the hidden part is the height of the sticky element, but I'm not certain if overflow-y: hidden conflicts with position: sticky (see Figure I). This behavior only occurs when using .scrollIntoView() while the expected behavior happens when manually scrolling up and down.

      Figure I enter image description here

    • In the example,

      • Moved the <footer> (sticky element) in the .scroller (in OP it was .scrollable-container).
      • Removed overflow-y: hidden from .buffer (in OP it was .main-container).
      • Wrapped everything except the <button> in a <main> and added overflow-y: hidden to <main>.

    const scrollButton = document.querySelector('button');
    scrollButton.onclick = () => {
      const item = document.querySelectorAll(".item")[2];
      item.scrollIntoView({
        behavior: 'smooth',
        block: 'center'
      });
    }
    html {
      font: 300 4ch/1 "Segoe UI"
    }
    
    button {
      font:inherit;
    }
    
    main {
      overflow-y: hidden;
    }
    
    .buffer {
      height: 300px;
      width: 50%;
      background-color: lightpink;
      padding: 16px;
    }
    
    .scroller {
      overflow-y: auto;
      height: 100%;
      width: 100%;
    }
    
    .item {
      min-height: 200px;
      margin-bottom: 8px;
      background-color: lightblue;
    }
    
    footer {
      padding: 48px 0px 12px 0px;
      pointer-events: none;
      z-index: 1;
      bottom: 0;
      position: sticky;
      text-align: center;
      background: linear-gradient( 180deg, rgba(255, 255, 255, 0) 19.3%, rgba(255, 255, 255, 0.981337) 77.3%);
    }
    <button>Scroll 3</button>
    
    <main>
      <section class="buffer">
        <div class="scroller">
          <div class="item">First item</div>
          <div class="item">Second item</div>
          <div class="item">Third item</div>
          <footer>Action</footer>
        </div>
      </section>
    </main>