JSFiddle: https://jsfiddle.net/8xo4aghn/
In this Fiddle, I have a menu which highlights the item corresponding to the current top section as the page is being scrolled. The last few sections may be short (as in this example) and they never get the menu selection. It's also possible to click the A-links to go to the section, but for the last few, the clicking won't select the item either.
I need to dynamically force any last short sections (if they exist) to at least correspond to menu clicks, if they'll never be activated via scrolling, so that the menu clicks make sense. Is this possible, or are there any better solutions to this scrolling problem? If I were to do it the other way - the last section being shown -- then clicking any non-last sections could conflict with that as well, and there would be similar discrepancies.
function updateMenu(){
const menus = document.querySelectorAll('div.sticky a');
const sectionsAll = document.querySelectorAll('div.card');
// Exclude any Sections that don't have an H2
const sections = [...sectionsAll].filter(section => {
const sectionH2 = section.querySelectorAll('h2');
const sectionHeader =
(sectionH2.length ? sectionH2[0] :
null);
if (!sectionHeader) {
return false; //exclude
} else {
return true;
}
});
let currentSection = null;
for (var i = 0; i < sections.length; i++) {
const rect = sections[i].getBoundingClientRect();
// Find current section; if not last in the list, stop immediately when found
if(rect.top >= 0) {
currentSection = sections[i];
if (i < sections.length - 1) {
break;
}
}
}
if(currentSection){
// Try to find a matching menu based on the A innerText
menus.forEach(menu => {
const menuInnerText = menu.innerText;
const currentSectionH2 = currentSection.querySelectorAll('h2');
const currentSectionHeader =
(currentSectionH2.length ? currentSectionH2[0] :
null);
if (currentSectionHeader && menuInnerText == currentSectionHeader.innerText) {
// Select menu
menu.classList.add('active');
} else {
// Clear menu
menu.classList.remove('active');
}
});
} else {
// Deselect all menus
menus.forEach(menu => {
menu.classList.remove('active');
});
}
}
window.addEventListener('scroll', updateMenu);
updateMenu(); // initial call
I was playing with this condition but it didn't work,
// Find current section; if not last in the list, stop immediately when found
if(rect.top >= 0) {
currentSection = sections[i];
if (i < sections.length - 1) {
break;
}
}
Based on Differentiate user scroll from anchor scroll i came up with the following solution to your issue
let userScrolling = true;
function highlightSection(section) {
const menus = document.querySelectorAll("div.sticky a");
if (section) {
// Try to find a matching menu based on the A innerText
menus.forEach((menu) => {
const menuInnerText = menu.innerText;
const currentSectionH2 = section.querySelectorAll("h2");
const currentSectionHeader = currentSectionH2.length ?
currentSectionH2[0] :
null;
if (
currentSectionHeader &&
menuInnerText == currentSectionHeader.innerText
) {
// Select menu
menu.classList.add("active");
} else {
// Clear menu
menu.classList.remove("active");
}
});
} else {
// Deselect all menus
menus.forEach((menu) => {
menu.classList.remove("active");
});
}
}
function updateMenu() {
if (!userScrolling) return;
// Attempt to find matching menu
const sections = document.querySelectorAll("div.card:has(h2)");
let currentSection = null;
for (var i = 0; i < sections.length; i++) {
const rect = sections[i].getBoundingClientRect();
// Find current section; if not last in the list, stop immediately when found
if (rect.top >= 0) {
currentSection = sections[i];
if (i < sections.length - 1) {
break;
}
}
}
highlightSection(currentSection);
}
document.querySelector(".sticky").addEventListener("click", (e) => {
const {
target
} = e;
if (target.nodeName === "A") {
userScrolling = false;
const id = new URL(e.target.href).hash;
if (id) {
const section = document.querySelector(id).closest(".card");
highlightSection(section);
}
}
});
window.addEventListener("wheel", () => (userScrolling = true));
window.addEventListener("touchmove", () => (userScrolling = true));
window.addEventListener("mousedown", () => (userScrolling = true));
window.addEventListener("scroll", updateMenu, {
passive: true
});
updateMenu(); // initial call
div.sticky {
position: sticky;
top: 0;
background-color: yellow;
padding: 5px;
font-size: 20px;
}
.card {
border: 1px solid gray;
margin-top: 20px;
}
.active {
border: 2px solid red;
}
/* Prevent A-anchor scrolling behind sticky header */
:target:before {
content: "";
display: block;
height: 90px;
/* fixed header height*/
margin: -90px 0 0;
/* negative fixed header height */
}
<div class="sticky">
<a href="#sec1Header">Section 1</a> | <a href="#sec2Header">Section 2</a> |
<a href="#sec3Header">Section 3</a> | <a href="#sec4Header">Section 4</a> |
<a href="#sec5Header">Section 5</a> | <a href="#sec6Header">Section 6</a> |
</div>
<div class="card">
<h2 id="sec1Header">Section 1</h2>
Some text some text Some text some text Some text some text Some text some
text Some text some text Some text some text Some text some text Some text
some text Some text some text
</div>
<div class="card">
<h2 id="sec2Header">Section 2</h2>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus imperdiet,
nulla et dictum interdum, nisi lorem egestas odio, vitae scelerisque enim
ligula venenatis dolor. Maecenas nisl est, ultrices nec congue eget, auctor
vitae massa. Fusce luctus vestibulum augue ut aliquet. Mauris ante ligula,
facilisis sed ornare eu, lobortis in odio. Praesent convallis urna a lacus
interdum ut hendrerit risus congue. Nunc sagittis dictum nisi, sed ullamcorper
ipsum dignissim ac. In at libero sed nunc venenatis imperdiet sed ornare
turpis. Donec vitae dui eget tellus gravida venenatis. Integer fringilla
congue eros non fermentum. Sed dapibus pulvinar nibh tempor porta. Cras ac leo
purus. Mauris
</div>
<div class="card">
<h2 id="sec3Header">Section 3</h2>
Nunc sagittis dictum nisi, sed ullamcorper ipsum dignissim ac. In at libero
sed nunc venenatis imperdiet sed ornare turpis. Donec vitae dui eget tellus
gravida venenatis. Integer fringilla congue eros non fermentum. Sed dapibus
pulvinar nibh tempor porta. Cras ac leo purus. Mauris quis diam velit. Lorem
ipsum dolor sit amet, consectetur adipiscing elit. Phasellus imperdiet, nulla
et dictum interdum, nisi lorem egestas odio, vitae scelerisque enim ligula
venenatis dolor. Maecenas nisl est, ultrices nec congue eget, auctor vitae
massa. Fusce luctus vestibulum augue ut aliquet. Mauris ante ligula, facilisis
sed ornare eu
</div>
<div class="card">
<h2 id="sec4Header">Section 4</h2>
abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc
abc abc abc abc abc
</div>
<div class="card">
<h2 id="sec5Header">Section 5</h2>
def def def def
</div>
<div class="card">
<h2 id="sec6Header">Section 6</h2>
ghijkslfkl ghijkslfkl ghijkslfkl ghijkslfkl
</div>
JSFiddle: https://jsfiddle.net/gaby/dfxw827b/35/