Как получить все записи, используя Intersection Observer API? - PullRequest
3 голосов
/ 26 января 2020

Я хочу сделать левую навигацию, показывающую текущий видимый раздел. Но мои секции имеют разную высоту, и я не могу понять, как правильно их отслеживать.

Было бы лучше, если бы моя функция обратного вызова при каждом вызове могла видеть все записи и их состояние, но она получала записи это соответствует пороговым значениям.

Возможно, я что-то упустил. Должен быть способ заставить это работать.

Вот мой jsBin https://jsbin.com/homibef/edit?html, css, js, вывод

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>

1 Ответ

0 голосов
/ 04 марта 2020

Это немного сложно, но это должно работать, как вы описали. Больше информации в 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>
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...