检查元素在视口中是否可见

Check if element is visible in viewport

我现在已经尝试了多种方法来做到这一点,我已经完成了 80%,但还不够。 我有不同大小的部分。当我在相应的部分时,我试图在导航中添加活动 class,显示它处于活动状态。

代码容易出现错误,在导航库中,我有一个砖石库和光滑的滑块我不确定这是否会影响它。但是活跃的 class 倾向于停留在画廊的导航中。 每个部分的高度至少为100vh。

根据 Cedric 的建议,我尝试使用 intersection observer API。 但它有同样的问题,至少在我的实现中,它是有问题的,即使不在视口中,画廊也会以某种方式处于活动状态。

  let options = {
    root: null,
    rootMargin: '0px',
    threshold: 1
  }
  let callback = (entries, observer) => {
      console.log("callback called");
    entries.forEach(entry => {
        console.log("set active for " + entry.target.id);
        let sectionId = entry.target.id;
        navItems.each(function(){
            $(this).removeClass('active');
        });
        $("a[href='#" + sectionId + "']").addClass('active');
    });
  };

  let observer = new IntersectionObserver(callback, options);

  pageSections.each(function () {
    let target = document.querySelector("#" + $(this).attr('id'));
    observer.observe(target);
  });

const navItems = $(".navigation-item");
const pageSections = $(".section-page");

const elementIsInView = el => {
    const scroll = window.scrollY || window.pageYOffset
    const boundsTop = el.getBoundingClientRect().top + scroll

    const viewport = {
        top: scroll,
        bottom: scroll + window.innerHeight,
    }

    const bounds = {
        top: boundsTop,
        bottom: boundsTop + el.clientHeight,
    }

    return ( bounds.bottom >= viewport.top && bounds.bottom <= viewport.bottom ) 
        || ( bounds.top <= viewport.bottom && bounds.top >= viewport.top );
}
$(function () {
    $(window).scroll(function () {
        pageSections.each(function () {
            console.log("elements to check " + $(this).attr('id'))
            if(elementIsInView($(this)[0])){
                console.log("element is in viewport " + $(this).attr('id'))
                // this = the section that is visible
                let sectionId = $(this).attr('id');
                navItems.each(function(){
                    $(this).removeClass('active');
                });
                $("a[href='#" + sectionId + "']").addClass('active');
            }
        })
    })
})   
.section-page{
  height:100vh;
}
.navigation-fixed{
  position:fixed;
  top:20px;
  left:20px;
}
.navigation-fixed ul li a.active{
  color:red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<nav class="navigation-fixed">
        <ul>
            <li>
                <a href="#nav-contact" class="navigation-item">CONTACT</a>
            </li>
            <li>
                <a href="#nav-info" class="navigation-item">INFO</a>
            </li>
            <li>
                <a href="#nav-gallery" class="naviation-item">GALLERY</a>
            </li>
            <li>
                <a href="#nav-home" class="navigation-item active">Home</a>
            </li>
        </ul>
    </nav>
    
    <section class="section-page" id="nav-gallery">Content here</section>
<section class="section-page" id="nav-info">Content here</section>
<section class="section-page" id="nav-contact">Content here</section>

除了 GALLERY 的 class 拼写错误外,您的代码看起来一切正常。一旦修复,它就不再粘了。

一般来说,如果您愿意向我们提供您所拥有的代码(顺便说一句,谢谢,它非常有帮助),如果您能使 Whosebugs 中的一个变得特别,那就太棒了 HTML+ JS+CSS 片段。它以与您相同的方式显示所有代码,但也允许将该代码 运行 并立即复制到可以对其进行编辑的答案。我在回答中使用了这样一个片段:

const navItems = $(".navigation-item");
const pageSections = $(".section-page");

const elementIsInView = el => {
    const scroll = window.scrollY || window.pageYOffset
    const boundsTop = el.getBoundingClientRect().top + scroll

    const viewport = {
        top: scroll,
        bottom: scroll + window.innerHeight,
    }

    const bounds = {
        top: boundsTop,
        bottom: boundsTop + el.clientHeight,
    }

    return ( bounds.bottom >= viewport.top && bounds.bottom <= viewport.bottom ) 
        || ( bounds.top <= viewport.bottom && bounds.top >= viewport.top );
}
$(function () {
    $(window).scroll(function () {
        pageSections.each(function () {
            if(elementIsInView($(this)[0])){
                // this = the section that is visible
                let sectionId = $(this).attr('id');
                navItems.each(function(){
                    $(this).removeClass('active');
                });
                $("a[href='#" + sectionId + "']").addClass('active');
            }
        })
    })
})   
.section-page {
  height: 100vh;
}

.active {
  color: red;
}

.navigation-fixed {
  position: fixed;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

<nav class="navigation-fixed">
  <ul>
    <li>
      <a href="#nav-contact" class="navigation-item">CONTACT</a>
    </li>
    <li>
      <a href="#nav-info" class="navigation-item">INFO</a>
    </li>
    <li>
      <a href="#nav-gallery" class="navigation-item">GALLERY</a>
    </li>
    <li>
      <a href="#nav-home" class="navigation-item active">Home</a>
    </li>
  </ul>
</nav>

<section class="section-page">Spacing</section>

<section class="section-page" id="nav-gallery">gallery here</section>
<section class="section-page">Spacing</section>

<section class="section-page" id="nav-info">info here</section>

<section class="section-page">Spacing</section>

<section class="section-page" id="nav-contact">contact here</section>
<section class="section-page">Spacing</section>

这是一个使用 Intersection Observer 的小/基本示例 我希望这会有所帮助

// list of elements to be observed
const targets = document.getElementsByClassName('section-page')

const options = {
  root: null, // null means root is viewport
  rootMargin: '0px',
  threshold: 0.5 // trigger callback when 50% of the element is visible
}


function callback(entries, observer) { 
  entries.forEach(entry => {

    if(entry.isIntersecting){
     document.querySelector('.active').classList.remove('active');
      const sectionId = entry.target.id; // identify which element is visible in the viewport at 50%
      document.querySelector(`[href="#${sectionId}"]`).classList.add('active');
      
    }
  });
};

let observer = new IntersectionObserver(callback, options);

[...targets].forEach(target => observer.observe(target));
body {
  margin: 0;
}

main {
  margin: 0;
  display: grid;
  grid-template-columns: 150px 1fr;
  font-family: sans-serif; 
}

#nav {
    list-style-type: none;
}

.navigation-item {
    color: inherit;
    text-decoration: none;
    display: block;
    margin-bottom: 12px;
    padding: 5px;
}

.navigation-item.active {
  background-color: grey;
  color: white;
  font-weight: bold;
}


#sections {
  height: 100vh;
  overflow: auto;
}

.section-page {
  margin: 20px;
  height: 100vh;
  box-sizing: border-box;
  padding: 0 20px 20px;
}

.section-page:nth-child(1) {
  background-color: crimson;
}

.section-page:nth-child(2) {
  background-color: darkgreen;
}

.section-page:nth-child(3) {
  background-color: darkorange;
}

.section-page:nth-child(4) {
  background-color: darkblue;
}

.content {
  padding-top: 20px;
  color: white;
  position: sticky;
  top: 0;
}
<main>
  <nav>
    <ul id="nav">
      <li>
        <a href="#nav-home" class="navigation-item active">HOME</a>
      </li>
      <li>
        <a href="#nav-gallery" class="navigation-item">GALLERY</a>
      </li>
      <li>
        <a href="#nav-info" class="navigation-item">INFO</a>
      </li>
      <li>
        <a href="#nav-contact" class="navigation-item">CONTACT</a>
      </li>
      
    </ul>
  </nav>
  <div id="sections">
    <section class="section-page" id="nav-home">
      <div class="content">Home section</div>
    </section>
    <section class="section-page" id="nav-gallery">
      <div class="content">Gallery section</div>
    </section>
    <section class="section-page" id="nav-info">
      <div class="content">Info section</div>
    </section>
    <section class="section-page" id="nav-contact">
      <div class="content">Contact section</div>
    </section>
  </div>
</main>