I'm trying to highlight the current section items in a sticky table of contents as you scroll down the page.
The structure is currently like:
<div>
<div>
<div>
<div>
<h2 id="twitter-chats-for-emerce">Header</h2>
<div>content...</div>
</div>
</div>
</div>
</div>
And the table of contents like:
<ul>
<li><a href="#">Item 1</a></li>
<li><a href="#">Item 1</a></li>
<li><a href="#twitter-chats-for-emerce">Twitter Chats for Emerce</a></li>
</ul>
The anchor is being applied to the header automatically via a Gutenberg block in WordPress (in this case, the h2).
The existing JavaScript for this is the following:
(function($) {
/* Table of contents - highlight active section on scroll */
window.addEventListener('DOMContentLoaded', () => {
const observer = new IntersectionObserver(entries => {
entries.forEach(entry => {
const id = entry.target.getAttribute('id');
if (entry.intersectionRatio > 0) {
document.querySelector(`nav li a[href="#${id}"]`).parentElement.classList.add('active');
} else {
document.querySelector(`nav li a[href="#${id}"]`).parentElement.classList.remove('active');
}
});
});
// Track all sections that have an `id` applied
document.querySelectorAll('h2[id]').forEach((section) => {
observer.observe(section);
});
});
})( jQuery );
But as you can see from the below example screenshot, the item in the table of contents is highlighting when the matching header (in this case h2) is visible on the page.
So in the case where there are multiple headers visible on the page, more than one item is highlighted in the TOC.
Ideally, I guess that it should be matching the div of the section but the id anchor is being applied to the header and I don't have control over this.
Is there a way I can modify the JS to somehow only detect the entire div section instead of just the header so that it will only ever have one item highlighted in the TOC - or is there even a better way than this?
An example of one that works perfectly that I'd like to achieve can be seen here (see the 'On This Page' section in right hand sidebar).
I'm trying to highlight the current section items in a sticky table of contents as you scroll down the page.
The structure is currently like:
<div>
<div>
<div>
<div>
<h2 id="twitter-chats-for-emerce">Header</h2>
<div>content...</div>
</div>
</div>
</div>
</div>
And the table of contents like:
<ul>
<li><a href="#">Item 1</a></li>
<li><a href="#">Item 1</a></li>
<li><a href="#twitter-chats-for-emerce">Twitter Chats for Emerce</a></li>
</ul>
The anchor is being applied to the header automatically via a Gutenberg block in WordPress (in this case, the h2).
The existing JavaScript for this is the following:
(function($) {
/* Table of contents - highlight active section on scroll */
window.addEventListener('DOMContentLoaded', () => {
const observer = new IntersectionObserver(entries => {
entries.forEach(entry => {
const id = entry.target.getAttribute('id');
if (entry.intersectionRatio > 0) {
document.querySelector(`nav li a[href="#${id}"]`).parentElement.classList.add('active');
} else {
document.querySelector(`nav li a[href="#${id}"]`).parentElement.classList.remove('active');
}
});
});
// Track all sections that have an `id` applied
document.querySelectorAll('h2[id]').forEach((section) => {
observer.observe(section);
});
});
})( jQuery );
But as you can see from the below example screenshot, the item in the table of contents is highlighting when the matching header (in this case h2) is visible on the page.
So in the case where there are multiple headers visible on the page, more than one item is highlighted in the TOC.
Ideally, I guess that it should be matching the div of the section but the id anchor is being applied to the header and I don't have control over this.
Is there a way I can modify the JS to somehow only detect the entire div section instead of just the header so that it will only ever have one item highlighted in the TOC - or is there even a better way than this?
An example of one that works perfectly that I'd like to achieve can be seen here (see the 'On This Page' section in right hand sidebar).
Share Improve this question asked Jan 29, 2021 at 12:15 zigojackozigojacko 2,06312 gold badges46 silver badges82 bronze badges3 Answers
Reset to default 6I modified randomdude's answer to highlight the lowest scrolled-to header. This will persist highlighting of that link until the user scrolls down far enough to another one.
const anchors = $('body').find('h1');
$(window).scroll(function(){
var scrollTop = $(document).scrollTop();
// highlight the last scrolled-to: set everything inactive first
for (var i = 0; i < anchors.length; i++){
$('nav ul li a[href="#' + $(anchors[i]).attr('id') + '"]').removeClass('active');
}
// then iterate backwards, on the first match highlight it and break
for (var i = anchors.length-1; i >= 0; i--){
if (scrollTop > $(anchors[i]).offset().top - 75) {
$('nav ul li a[href="#' + $(anchors[i]).attr('id') + '"]').addClass('active');
break;
}
}
});
forked fiddle link: http://jsfiddle/tz6yxfk3/
This is same answer as above, but is written in pure JavaScript, no need for jQuery. Great work, Crane, works like a champ.
const anchors = document.querySelectorAll('h1');
const links = document.querySelectorAll('nav > ul > li > a');
window.addEventListener('scroll', (event) => {
if (typeof(anchors) != 'undefined' && anchors != null && typeof(links) != 'undefined' && links != null) {
let scrollTop = window.scrollY;
// highlight the last scrolled-to: set everything inactive first
links.forEach((link, index) => {
link.classList.remove("active");
});
// then iterate backwards, on the first match highlight it and break
for (var i = anchors.length-1; i >= 0; i--) {
if (scrollTop > anchors[i].offsetTop - 75) {
links[i].classList.add('active');
break;
}
}
}
});
I think you are looking for this:
$(window).scroll(function(){
var scrollTop = $(document).scrollTop();
var anchors = $('body').find('h1');
for (var i = 0; i < anchors.length; i++){
if (scrollTop > $(anchors[i]).offset().top - 50 && scrollTop < $(anchors[i]).offset().top + $(anchors[i]).height() - 50) {
$('nav ul li a[href="#' + $(anchors[i]).attr('id') + '"]').addClass('active');
} else {
$('nav ul li a[href="#' + $(anchors[i]).attr('id') + '"]').removeClass('active');
}
}
});
nav {
position: fixed;
right: 20px;
top: 20px;
}
div {
height: 2000px;
}
div > h1 {
height: 200px;
}
.active {
color: red;
}
<script src="https://cdnjs.cloudflare./ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<nav>
<ul>
<li id="whatbutton"><a href="#whatissm">What We Are</a></li>
<li id="whybutton"><a href="#whyusesm">Why Us</a></li>
<li id="offerbutton"><a href="#whatdoessmoffer">What We Offer</a></li>
<li id="contactbutton"><a href="#contactus">Contact Us</a></li>
</ul>
</nav>
<div>
<h1 id="whatissm" name="whatissm"><span>sometexthere</span></h1>
<h1 id="whyusesm" name="whyusesm"><span>somtexthere</span></h1>
<h1 id="whatdoessmoffer" name="whatdoessmoffer"><span>sometexthere</span></h1>
<h1 id="contactus" name="contactus"><span>Contact Us</span></h1>
</div>
credits: Making a menu class active when scrolled past
fiddle link: http://jsfiddle/yrz54fqm/1/