Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to get all entries using Intersection Observer API?

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>
like image 698
Serge Avatar asked Jan 26 '20 13:01

Serge


People also ask

What are entries in intersection observer?

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.

Can intersection observer observe multiple elements?

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.

What is the intersection Observer API?

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 is the Intersection Observer API?

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 intersectionobserver do?

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 .

What is Anan Intersection Observer?

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.

How do I create an intersection observer with a threshold?

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.


1 Answers

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>
like image 157
bigless Avatar answered Oct 26 '22 23:10

bigless