I want to make left side navigation showing the current visible section. But my sections have different height and I can't figure out how to properly track them.
It would be better if my callback function every time it is invoked could see all entries and their state, but it gets entries that matches threshold values.
I probably missing something out. There should be a way to make this working.
Here is my jsBin https://jsbin.com/homibef/edit?html,css,js,output
stickybits('#sticky', { stickyBitStickyOffset: 0 });
if (window.IntersectionObserver) {
const callback = function(entries) {
// Find all visible and then with biggest intersectionRatio
let currentNav = null;
const visibleEntries = entries.filter(entry => entry.isIntersecting);
if ( Array.isArray(visibleEntries) && visibleEntries.length > 0 ) {
currentNav = visibleEntries.reduce((prev, current) => {
return (prev.intersectionRatio > current.intersectionRatio) ? prev : current;
});
} else {
currentNav = visibleEntries;
}
if (currentNav.target) {
// Handle navigation change
const wasCurrent = document.querySelector('.navItem.isCurrent');
if ( wasCurrent ) {
wasCurrent.classList.remove('isCurrent');
}
const currentName = currentNav.target.getAttribute('name');
const current =
document.querySelector(`.navItem[data-link='${currentName}']`);
current.classList.add('isCurrent');
}
};
const observer = new IntersectionObserver(callback, {
root: null,
rootMargin: '0px',
threshold: [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1],
});
const section = document.querySelectorAll('section');
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 data-link="section1" class="navItem">Section 1</li>
<li data-link="section2" class="navItem">Section 2</li>
<li data-link="section3" class="navItem">Section 3</li>
<li data-link="section4" class="navItem">Section 4</li>
</ul>
</div>
</div>
<!-- Content -->
<div class="content">
<section name="section1" class="section1"></section>
<section name="section2" class="section2"></section>
<section name="section3" class="section3"></section>
<section name="section4" class="section4"></section>
</div>
</div>
</body>
</html>
The IntersectionObserverEntry interface of the Intersection Observer API describes the intersection between the target element and its root container at a specific moment of transition.
observe() The IntersectionObserver method observe() adds an element to the set of target elements being watched by the IntersectionObserver . One observer has one set of thresholds and one root, but can watch multiple target elements for visibility changes in keeping with those.
The Intersection Observer API lets code register a callback function that is executed whenever an element they wish to monitor enters or exits another element (or the viewport), or when the amount by which the two intersect changes by a requested amount.
The Intersection Observer API lets code register a callback function that is executed whenever an element they wish to monitor enters or exits another element (or the viewport ), or when the amount by which the two intersect changes by a requested amount.
What Can the Intersection Observer Do? The IntersectionObserver API lets you register a callback function which is executed whenever an element being monitored enters or exits another element, or the viewport .
An Intersection Observer is a browser API that provides a way to observe the visibility and position of a DOM element relative to the containing root element or viewport. The API is asynchronous, giving a smooth user experience. Some common use cases of this API include lazy-loading images on scroll, implementing infinite scrolling, and animations.
Create the intersection observer by calling its constructor and passing it a callback function to be run whenever a threshold is crossed in one direction or the other: A threshold of 1.0 means that when 100% of the target is visible within the element specified by the root option, the callback is invoked.
It is little bit tricky, but it should work as you described. More info in javascript comments:
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>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With