Skip to main content
 首页 » 编程设计

javascript之如何使用 Intersection Observer API 获取所有条目

2025年05月04日9birdshome

我想让左侧导航显示当前可见部分。但是我的部分有不同的高度,我不知道如何正确跟踪它们。

如果我的回调函数每次被调用时都能看到所有条目及其状态,但它会获得与阈值匹配的条目,那会更好。

我可能错过了一些东西。应该有办法让这个工作。

这是我的 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>

请您参考如下方法:

这有点棘手,但它应该像你描述的那样工作。 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>