Adding position:sticky to element causes jumpy behaviour on scroll

Issue

Problem:
I have a title container element that starts off as 100vh to act as a landing page. When the user scrolls it should reduce the title container element to 15vh and stick it to the top of the window.
I’ve essentially done that but the transition isn’t very smooth and jumps around when the user doesn’t scroll heaps.

Intended outcome:
When the user scrolls at all, it should trigger the title element to reduce in size to 15vh and stick to the top of the window with a smooth transition.

Things tried:

  • Adding additional transitions to the elements below the title element.
  • changing the initial position of the title to ‘absolute’
  • Instead of adding in a class with position:sticky, I tried using position:absolute and top:0px.
  • I have seen this post, but not sure how to apply it to mu issue.

I have tried to give an example below with the minimum amount of code.

window.addEventListener(
  'scroll',
  function() {
    let scrollTop =
      window.pageYOffset ||
      (document.documentElement || document.body.parentNode || document.body)
      .scrollTop;
    getPosition();
    console.log(getPosition());
    if (getPosition() <= 0) {
      this.document.getElementById('title').classList.add('header');
    } else {
      this.document.getElementById('title').classList.remove('header');
    }
  },
  false
);

function getPosition() {
  element = document.getElementById('topTitleMarker');
  var clientRect = element.getBoundingClientRect();
  return clientRect.top;
}
.title {
  padding: 0;
  margin: 0;
  height: 100vh;
  font-weight: bold;
  font-family: 'Titillium Web', sans-serif;
  display: flex;
  justify-content: center;
  align-items: center;
  width: 100%;
  background-color: black;
  -webkit-transition: all 1s linear;
  -moz-transition: all 1s linear;
  -o-transition: all 1s linear;
  transition: all 1s linear;
  color: white;
}

.header {
  top: 0px;
  height: 15vh;
  position: sticky;
}

body {
  background-color: white;
  height: 500vh;
  -ms-overflow-style: none;
  scrollbar-width: none;
  border-bottom: 5px solid white;
  /*   overflow: hidden; */
}

.topTitleMarker {
  top: 1px;
  position: absolute;
}

.test {
  margin-top: 15vh;
}
<div id='title' class="title">TITLE</div>
<div id='topTitleMarker' class="topTitleMarker"></div>
<div class="test">TEST</div>

Codepen

Solution

Problems

I think the jerkiness in the scrolling was because of the .topTitleMarker was referenced as the top of scrolling element with .getBoundingClientRect().top and the transition was based on the height of .title when it increases at 100vh but there isn’t a transition for when .title shrinks. Moreover, transition is a process heavy animation, but your animation is a simple one so no worries there.

Changes

JavaScript

  • Removed scrollTop — it wasn’t being used
  • Moved and consolidated getPosition() over to the event handler
  • Also changed the reference element for .getBoundingClientRect().top from
    .topTitleMarker to <h1> (in OP it’s .title)

CSS

  • Removed transition from <h1>
  • Added animation: grow 1s forwards ease-out; to <h1>
  • Added animation: shrink 1s forwards ease-out; to .header
  • For smoother animation animation and @keyframes are used for shrinking and growing of <h1>

HTML

  • Removed everything from OP and added a <h1>
window.addEventListener('scroll', headerHeight);

function headerHeight(event) {
  const title = document.querySelector('h1');
  if (title.getBoundingClientRect().top <= 0) {
    title.classList.add('header');
  } else {
    title.classList.remove('header');
  }
};
@import url('https://fonts.googleapis.com/css2?family=Titillium+Web:wght@300;700&display=swap');

html {
  font: 300 2ch/1.25 'Titillium Web';
}

body {
  height: 500vh;
}

h1 {
  display: flex;
  justify-content: center;
  align-items: center;
  width: 100%;
  height: 100vh;
  margin: 0;
  padding: 0;
  font-weight: 700;
  color: white;
  background-color: black;
  animation: grow 1s forwards ease-out;
}

.header {
  position: sticky;
  top: 0px;
  height: 15vh;
  animation: shrink 1s forwards ease-out;
}

@keyframes grow {
  0% {
    height: 15vh
  }
  100% {
    height: 100vh
  }
}

@keyframes shrink {
  0% {
    height: 100vh
  }
  100% {
    height: 15vh
  }
}
<h1>TITLE</h1>

Answered By – zer00ne

Answer Checked By – Clifford M. (AngularFixing Volunteer)

Leave a Reply

Your email address will not be published.