Это немного сложно, но это должно работать, как вы описали. Больше информации в javascript комментариях:
stickybits('#sticky', { stickyBitStickyOffset: 0 });
if (window.IntersectionObserver) {
const section = document.querySelectorAll('section');
const sectionArr = Array.from(section);
const callback = function(entries) {
// this is intialized for all targets
// after that, only entries which pass threshold(any) in same viewport position(scroll)
// thus it will be most likely one or two sections, not all
for (entry of entries) {
// setting properties on native Objects is ugly, but most straightforward
// instead of intersectionRatio, we want intersectionRect to compare height
// more tresholds => more precise behaviour
// step 0.1 = 10%, 10% of 3000px height section = 300px => pretty large breakpoints
entry.target._intersectionHeight = entry.intersectionRect.height;
}
// compare visibility of sections(all) after every intersection
const mostVisibleSection = sectionArr.reduce((prev, current) => {
if (current._intersectionHeight > (prev ? prev._intersectionHeight : 0)) {
return current;
} else {
return prev;
}
}, null);
// TIP: you can store this variable outside of callback instead of selecting
const prevMostVisibleLink = document.querySelector('.isCurrent');
// no section is visible
if (!mostVisibleSection) {
prevMostVisibleLink && prevMostVisibleLink.classList.remove('isCurrent');
return;
}
// ok, there is most visible section, lets target link
const mostVisibleLink = document.getElementById(mostVisibleSection.dataset.id);
if (mostVisibleLink !== prevMostVisibleLink) {
prevMostVisibleLink && prevMostVisibleLink.classList.remove('isCurrent');
mostVisibleLink.classList.add('isCurrent');
}
};
// zero covers also entries comming out of viewport
const observer = new IntersectionObserver(callback, {
threshold: [0, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.65, 0.7, 0.75, 0.8, 0.85, 0.9, 0.95, 1],
});
section.forEach(item => observer.observe(item));
}
* {
margin: 0;
padding: 0;
}
.row {
width: 100%;
display: flex;
flex-wrap: nowrap;
}
.navigation {
margin-right: 50px;
}
.navItem {
color: #666;
}
.navItem.isCurrent {
font-weight: bold;
color: #000;
}
.content {
width: 100%;
flex: 1;
}
section {
padding: 10px;
width: 200px;
border-radius: 10px;
margin-bottom: 30px;
}
.section1 {
height: 800px;
background: #90ee90;
}
.section2 {
height: 200px;
background: #add8e6;
}
.section3 {
height: 150px;
background: #808080;
}
.section4 {
height: 400px;
background: #800080;
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>JS Bin</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/stickybits/3.7.3/stickybits.min.js"></script>
</head>
<body>
<div class="row">
<!-- Navigation -->
<div class="navigation">
<div id="sticky" class="sticky">
<ul class="nav">
<li id="section1" class="navItem">Section 1</li>
<li id="section2" class="navItem">Section 2</li>
<li id="section3" class="navItem">Section 3</li>
<li id="section4" class="navItem">Section 4</li>
</ul>
</div>
</div>
<!-- Content -->
<div class="content">
<section name="section1" class="section1" data-id="section1"></section>
<section name="section2" class="section2" data-id="section2"></section>
<section name="section3" class="section3" data-id="section3"></section>
<section name="section4" class="section4" data-id="section4"></section>
</div>
</div>
</body>
</html>