Author: Stasyuk Eugene 47 Tags: 14.12.2022

What’s this?

On some sites, you can periodically find something like this:

nav

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');

                            }
                    });
            });
    }
}

Other articles

DOMSubtreeModified – event on element change action

DOMSubtreeModified – event on element change

DOMSubtreeModified – event on element change

Brief background I had a website in which the forms were configured on the Contact Form 7 plugin. These forms themselves were integrated with Mailchimp. The challenge was this: after a user fills out and submits a certain form, the message should go to the service with a certain tag (Mailchimp tags are the easiest […]