What’s this?
On some sites, you can periodically find something like this:
This thing works like this:
- Each content item is an article heading h2, h3, h4…
- By clicking on any of these items, we move with the help of an anchor link to the area of the page we need.
- Also, when we scroll through the page and a certain area with a heading enters the user’s field of view, this navigation item is somehow highlighted, making it clear where we are.
You can download the finished file from this link
How it works?
And now we will analyze everything in stages. We will collect the script file in pieces.
Our navigation markup will look like this:
<ul>
<li>
<a href="#article_title1">Title H2</a>
<ul>
<li>
<a href="#article_title2">Title H3</a>
</li>
<li>
<a href="#article_title3">Title H4</a>
</li>
</ul>
</li>
<li>
<a href="#article_title4">Title H2</a>
<ul>
<li>
<a href="#article_title5">Title H3</a>
</li>
<li>
<a href="#article_title6">Title H4</a>
</li>
</ul>
</li>
</ul>
As you can see from the example, our navigation will be nested.
1) We create a variable in which we place the content headers in the area we need:
const titles = document.querySelector('.post-content__content .text').querySelectorAll('h2,h3,h4');
2) Adding id to our headers:
let i = 0;
titles.forEach(title=>{
title.setAttribute('id', `articles_title${i}`);
i++;
});
3) We create an object and a variable that will help us make nesting
let titlesObj = {},
h2;
for(let i = 0; i < titles.length; i++){
if(titles[i].classList.contains('no-active')){
continue;
}
if(titles[i].tagName == 'H2'){
titlesObj[titles[i].textContent] = {
'id': titles[i].getAttribute('id')
};
if(titles[i+1] && titles[i+1].tagName != 'H2'){
h2 = titles[i].textContent;
titlesObj[titles[i].textContent]['titles'] = {};
}
}
if(titles[i].tagName != 'H2'){
titlesObj[h2]['titles'][titles[i].textContent] = {
'id': titles[i].getAttribute('id')
}
}
}
4) Building the structure of our content
let navigation = '';
for(let item in titlesObj){
let subLinks = '';
if(titlesObj[item]['titles']){
subLinks = subLinks + '<ul>';
for(let sub in titlesObj[item]['titles']){
subLinks = subLinks + `<li><a href="#${titlesObj[item]['titles'][sub]['id']}">${sub}</a></li>`;
}
subLinks = subLinks + '</ul>';
}
navigation = navigation + `<li><a href="#${titlesObj[item]['id']}">${item}</a>${subLinks}</li>`;
}
5) Adding navigation markup to the area we need
document.querySelector('#articles_nav ul').innerHTML = navigation;
6) We create and call a function that will be:
– smoothly move the page to the area with the title when clicking on the navigation item.
– will highlight the desired menu item when scrolling the page.
postContentNavigation();
function postContentNavigation(){
// 6.1) We store all navigation links in a variable
const smoothLinks = document.querySelectorAll('#articles_nav a');
if(smoothLinks){
// 6.2) We hang a click event on each link
for (let smoothLink of smoothLinks) {
smoothLink.addEventListener('click', function (e) {
e.preventDefault();
const id = smoothLink.getAttribute('href');
document.querySelector(id).scrollIntoView({
behavior: 'smooth',
block: 'start'
});
});
};
// 6.3) Highlight menu items
$(window).scroll(function(){
var $sections = $('.post-content__content h2, .post-content__content h3, .post-content__content h4');
$sections.each(function(i,el){
var top = $(el).offset().top-50;
var bottom = top +$(el).height();
var scroll = $(window).scrollTop();
var id = $(el).attr('id');
if( scroll > top && scroll < bottom){
$('li.active').removeClass('active');
$('a[href="#'+id+'"]').parents('li').addClass('active');
}
});
});
}
}