Scroll Events

Probably the highest occurring event in a browser window is the scroll event. When the user scrolls a page, or an element, the scroll event fires. The scroll event lets us know which part of the page the user is viewing, so we could use this information to change some elements' CSS, or switch off some calculation heavy animation that is not in view anymore.

Lets begin with a simple example. We will create a page with a set height. Then we will listen for the scroll event on window. Whenever the user scrolls, we will update a percentage number on the screen fixed to the top left corner that shows how much of the page has left to scroll.

<div id="progress">0%</div>
body {
  margin: 0;
  height: 3000px;
}
#progress {
  position: fixed;
  top: 20px;
  left: 20px;
}
var progress = document.body.querySelector('#progress');
window.addEventListener('scroll', function (event) {
  var scrollableHeight = document.body.scrollHeight - window.innerHeight;
  var percentage = Math.round((window.pageYOffset / scrollableHeight) * 100);
  progress.innerHTML = percentage + "%";
});

A bit of sidetrack here. In order for us to calculate the percentage of the page a user has viewed, we need to know the total scrollable height the user can scroll through. There are a few properties we need to look at to get the number we need.

Element.scrollHeight: This is the full height of an element's content plus padding, including content that is not visible on the screen. It excludes horizontal scrollbar height, border and margin. If we have a div element that has a lot of content, such as paragraphs, we could limit the div's viewable height to say 500px. The scrollHeight of the div in this case would be larger than its CSS defined height.

Element.clientHeight: Extending the example described above, the clientHeight of the div would be the visible area displayed on the screen. clientHeight includes padding but does not include horizontal scrollbar height.

scrollHeight clientHeight

window.innerHeight: This is the height of the window's viewport including the height of the horizontal scrollbar. In other words, this is the height of the screen that is displaying the page.

innerHeight

Because a part of the body is always going to be displayed on the screen, the height that is left to be scrolled would be the full content height of the page, minus the height of the window; document.body.scrollHeight - window.innerHeight.

To get how much the user has scrolled so far, we can query window.pageYOffset, it is measured in px. So the percentage the user has scrolled is just (window.pageYOffset / document.body.scrollHeight - window.innerHeight) * 100.

Try it on Repl

Detecting End of Scroll on Element

The above example works on the body element and when the scroll event happens on the window. Sometimes, we might have a smaller element on the page that we require the user to read but is contained by a max height value. Such could be the Terms and Conditions during user account creation. When dealing with an element that has a defined height, and its content overflowed, we need to query different properties. Imagine the following example.

<form>
  <div id="terms">
    <p>
      Lorem ipsum dolor sit amet, consectetur adipiscing elit. In ut ipsum id lectus tempus viverra. ...
    </p>
  </div>
  <input type="checkbox" id="agree" name="agree" disabled/>
  <label for="agree">I Agree</label>
</form>
#terms {
  height: 200px;
  border: 1px solid black;
  border-radius: 2px;
  overflow: scroll;
}

The p element's actual content height is larger than the 200px set on #terms. We set #terms to overflow: scroll; so the user needs to scroll the content to view all the text. We want to know when the user has scrolled all the way to the end. Then we can enable the check box input at the bottom.

This is very similar to the first case, we have to listen for the scroll event on #terms. The calculation to find out if the user has reached the end is theoretically the same, the only thing changed is the properties we need to query.

To get the full content height of the scrollable element #terms, we can query the Element.scrollHeight property. To figure out how much of this height is actually scrollable, we need to determine how much of the content is hidden away. We can check Element.clientHeight to find out the height of the visible area of an element. Naturally, the height of the content that is hidden away is just scrollHeight - clientHeight

scrollHeight clientHeight

Now we just need to know how much the user has scrolled so far. Each scrollable element has a property called Element.scrollTop which returns in px how much the content is scrolled vertically.

We will know the user has scrolled to the bottom of the #terms element when scrollHeight - clientHeight is equal to scrollTop.

var terms = document.body.querySelector('#terms');
var agree = document.body.querySelector('#agree');

var enableCheckbox = function (event) {
  var scrollableHeight = terms.scrollHeight - terms.clientHeight;
  if (scrollableHeight === terms.scrollTop) {
    agree.disabled = false;
    terms.removeEventListener('scroll', enableCheckbox);
  }
}
terms.addEventListener('scroll', enableCheckbox);

It's also good to clean up after ourselves, that's why we removed the handler once our checkbox is enabled.

Info: Element.scrollTop is also a settable property, you can use it to set the scroll distance of an element from its top.

Try it on Repl

Fixed Top NavBar With Changing Background Color

Lets go through another example that is commonly applied in the real world. To make browsing more convenient for users, many designers will set the navigation bar fixed at top. The designer would often apply a different set of styles to the nav bar when it's in its initial position compared to when the page has been scrolled. We can easily do this by listening to the scroll event on window then toggle a class on the nav bar that changes the styles.

<body>
  <nav id="top-nav">...</nav>
  <div id="hero">...</div>
  <div>Content</div>
</body>
#top-nav {
  padding: 20px;
  position: fixed;
  top: 0;
  width: 100%;
}

#top-nav a {
  color: #FFF;
}

#top-nav.inverse {
  background-color: rgba(255, 255, 255, 0.5);
}

#top-nav.inverse a {
  color: #1976D2;
}

We would only apply the .inverse class to nav only after we have scrolled pass the #hero element. So in our event handler, we need to know the height of the #hero element.

var nav = document.body.querySelector('#top-nav');
var hero = document.body.querySelector('#hero');

window.addEventListener('scroll', function (event) {
  if (window.pageYOffset > hero.scrollHeight) {
    nav.classList.add('inverse');
  } else {
    nav.classList.remove('inverse');
  }
});

Try it on Repl

results matching ""

    No results matching ""