This scroll indicator will display titles for the current section you have scrolled to. You can see a working example here.
If you haven't created the scroll indicator yet, go here to see the tutorial on that.
We will add a title element to the existing slider html markup:
HTML
<div class="slider-wrapper">
<h3 id="progress-title">This is the top</h3>
<div id="slider" style="width: 0%"></div>
</div>
We'll add a little css styling to the title element:
CSS
#progress-title{
position: absolute;
padding: 6px 10px;
}
We also need to create a class for all the titles of each section we have throughout our document:
HTML
<section>
<h2 class="title">Some title for my section</h2>
<p>
Lorem ipsum...
</p>
</section>
<section>
<h2 class="title">Some other title for my other section</h2>
<p>
Lorem ipsum...
</p>
</section>
//etc...
It's time for the JavaScript part. Now we already have some code written and we just need to add some more functionality to this:
JavaScript code
const slider = document.querySelector("#slider");
document.addEventListener("scroll", function(){
const totalScrollDistance =
document.documentElement.scrollHeight -
document.documentElement.clientHeight;
const progress = scrollY * 100 / totalScrollDistance;
slider.style.width = progress + "%";
});
First let's restructure our code to make it easier to read and manage. We simply take the code from inside
the event listener and place it in a function of its own called scrollProgress,
and then call this function from the event listener instead.
there is absolutely no difference to the functionality of the code here as opposed to the code above.
This is just another way to write the "same" code, which is considered "cleaner"
JavaScript code
const slider = document.querySelector("#slider");
document.addEventListener("scroll", function(){
scrollProgress();
});
//This code would also work just the same:
//document.addEventListener("scroll", scrollProgress);
//
//Here we just call the function directly instead of
//calling an anonymous function which in turn calls scrollProgress.
//We won't use that here since we are going to add more
//functionality to our anonymous function later on.
function scrollProgress(){
const totalScrollDistance =
document.documentElement.scrollHeight -
document.documentElement.clientHeight;
const progress = scrollY * 100 / totalScrollDistance;
slider.style.width = progress + "%";
}
We will then add a reference to the title element of our slider as well as a reference to all our titles we have added throughout the document the querySelectorAll which will grab all titles with a class="title" and put them in an array We'll get to the explanation of the array part later:
JavaScript code
const slider = document.querySelector("#slider");
const progressTitle = document.querySelector("#progress-title");
const titles = document.querySelectorAll(".title");
document.addEventListener("scroll", function(){
scrollProgress();
});
function scrollProgress(){
const totalScrollDistance =
document.documentElement.scrollHeight -
document.documentElement.clientHeight;
const progress = scrollY * 100 / totalScrollDistance;
slider.style.width = progress + "%";
}
Next we will add the code that will figure out which title to display and then display it. Read on to get the explanation for this code:
JavaScript code
const slider = document.querySelector("#slider");
const progressTitle = document.querySelector("#progress-title");
const titles = document.querySelectorAll(".title");
document.addEventListener("scroll", function(){
scrollProgress();
getTitle();
});
function scrollProgress(){
const totalScrollDistance =
document.documentElement.scrollHeight -
document.documentElement.clientHeight;
const progress = scrollY * 100 / totalScrollDistance;
slider.style.width = progress + "%";
}
function getTitle(){
for (let i = titles.length - 1; i >= 0; i--){
const titlePosition = titles[i].getBoundingClientRect().top;
if (titlePosition <= 0) {
progressTitle.innerHTML = titles[i].innerHTML;
return;
}
}
progressTitle.innerHTML = "This is the top";
}
Now we come to the part with the array that i promised to explain.
When we reverse the loop, we check each title from the last to the first. This way we can check if the
position of the title is lower or equal to zero and thus give us the correct title.
At first we will loop through this array called titles with a reversed for loop
a reversed for loop just loops through the array backwards, from the last element in the array to the first,
instead of from the beginning to the end
and when/if the if statements condition is true the code will exit completely after setting the correct titles content.
The reason for the reversed loop is to make it easier to check each titles position relative to the current
scroll position.
Wait what? I don't understand this reversed thing!
In contrast, if we use a traditional for loop, and check if the titles position is lower than zero,
we run into problems. The check for the first title will work just fine, but the second (and all others) will fail
because the condition for checking the first element in the array will always hold true.
Let's visualize this with a theoretical example...
Let's say we have scrolled to a position at 1000px and we have three titles.
The first title starts at 200px and when scrolled 1000px the position of the title is
-800px (200px minus 1000px), the second start at 800px and is at -200px (800px - 1000px)
and a third one that starts at 1500px which places it at 500px (1500 - 1000),
which means we are at the second title (we have just scrolled past the
second at -200px, and haven't reached the third yet at 500px).
In the for loop we check the first item, then the next and so on. So the check on the first element goes something
like this:
"if (-800px <= 0)". This will hold true and therefore the code will
stop and give us the first titles content, which isn't what we want.
If we visualize this with the reversed for loop, the check will happen with the third item first, then the second
and lastly the first. So the check for the last item (which is the third title element) goes:
"if (500px <= 0)", this is not true and just what we expect (we want the second title at
-200px), so next check:
"if (-200px <= 0)", great, this is true so the reversed for loop gives us
exactly what we want, namely the second title.
First we set a variable "i" to a value of the length of the array
("titles.length", the number of items in the array) minus one.
Minus one because the index of the first item is always zero, so if there are three items in the array the last item must be three
minus one which is two (two because of the indexes: 0, 1 and 2), this is the starting value.
Secondly we define a condition where as long as the condition isn't met, the loop continues. The condition in our case is that as
long as the value of "i" is greater or equal to zero, keep on going.
Thirdly we tell the loop to decrement the value of "i" by one for each runthrough of the loop:
JavaScript code
function getTitle(){
for (let i = titles.length - 1; i >= 0; i--){
const titlePosition = titles[i].getBoundingClientRect().top;
if (titlePosition <= 0) {
progressTitle.innerHTML = titles[i].innerHTML;
return;
}
}
progressTitle.innerHTML = "This is the top";
}
In the first line inside the loop, we declare a variable and give it the value of the top position of the title from the array.
So the first run in the loop, "titles[i]" will hold a reference to the last title element and "getBoundingClientRect()" is a
built in function that basically can return the size and/or the position of the element, relative to the viewport. To get the top position
of the element, we write "getBoundingClientRect().top".
The second time around in the loop, the variable
"i" is decremented by one, so "title[i]" will now hold a reference to
the second last item in the array. This will continue until "i" gets to zero which will make it a
reference to the very first item in the array or the very first title element, if you will:
JavaScript code
function getTitle(){
for (let i = titles.length - 1; i >= 0; i--){
const titlePosition = titles[i].getBoundingClientRect().top;
if (titlePosition <= 0) {
progressTitle.innerHTML = titles[i].innerHTML;
return;
}
}
progressTitle.innerHTML = "This is the top";
}
Next we create a condition that basically checks which title element we have scrolled past and are also closest to.
The logic of this check with the statement if (titlePosition <= 0)
translates to: "if the top position of the title is less than or equal to zero" which means that if the title element scrolls past
the very top of the viewport (which is at position 0) it is the title we want to get our hands on or said another way,
it's the section we are currently looking at in the browsers viewport.
JavaScript code
function getTitle(){
for (let i = titles.length - 1; i >= 0; i--){
const titlePosition = titles[i].getBoundingClientRect().top;
if (titlePosition <= 0) {
progressTitle.innerHTML = titles[i].innerHTML;
return;
}
}
progressTitle.innerHTML = "This is the top";
}
If the condition is true, the progress title (the one at the scroll indicator) will be set with the innerHTML of the
title from the array (the title from the section we are currently looking at).
And then we exit the function (return;), so we never get to the last line:
JavaScript code
function getTitle(){
for (let i = titles.length - 1; i >= 0; i--){
const titlePosition = titles[i].getBoundingClientRect().top;
if (titlePosition <= 0) {
progressTitle.innerHTML = titles[i].innerHTML;
return;
}
}
progressTitle.innerHTML = "This is the top";
}
In case we loop through the entire array which will only happen if the condition in the if statement never holds true
(meaning we are at the top of the page), we move on to the last line.
Here we set the progress title to a default value (the string "This is the top"):
JavaScript code
function getTitle(){
for (let i = titles.length - 1; i >= 0; i--){
const titlePosition = titles[i].getBoundingClientRect().top;
if (titlePosition <= 0) {
progressTitle.innerHTML = titles[i].innerHTML;
return;
}
}
progressTitle.innerHTML = "This is the top";
}
I hope this tutorial wasn't too confusing, and if you succeded in creating the scroll indicator, kudos to you...