简体   繁体   中英

Check if element is visible in viewport

I have tried multiple ways to do this now, I am 80% there but it's still not quite there. I have sections of different sizes. And my navigation where I am trying to add an active class when I'm on the corresponding section, showing that it is active.

The code has a tendency to be buggy, inside nav-gallery, I have a masonry gallery, and slick slider I'm not sure if that affects it. But the active class has a tendency to stick on the gallery, in the navigation. Each section is at least 100vh in height.

Per a suggestion from Cedric, I tried with intersection observer API . But it has the same problem, at least in my implementation, it is buggy and gallery somehow is active even when not in the viewport.

  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>

Everything looks fine with your code except for mispelling the class for GALLERY. Once that's fixed it's no longer sticky.

In general, when you're willing to provide us with code like you have (thank you btw, it was very helpful) it'd be awesome if you could make one of StackOverflows special HTML+JS+CSS snippets. It shows all the code the same way you have, but also allows for that code to be run and instantly copied to an answer where it can be edited a bit. I've used such a snippet for my answer:

 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>

Here is a small / basic example using Intersection Observer I hope this will help

 // 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>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM