5

This is my code so far:

const mediaInViewport = document.querySelectorAll('.media');
const links = Array.from(document.querySelectorAll('.link'));
let actLink = links[0];

document.body.addEventListener('click', (event) => {
  if (event.target.tagName === 'a') {
    actLink.classList.remove('active');
    actLink = links.find(link => event.target.href === link.href)
    actLink.classList.add('active');
  }
}, false)

observer = new IntersectionObserver((entries, observer) => {
  entries.forEach((entry) => {
    if (entry.target && entry.isIntersecting) {
      const closestParent = entry.target.closest('section');
      if (closestParent) {
        actLink.classList.remove('active');
        actLink = links.find(link =>
          link.href.slice(link.href.lastIndexOf('#')) === `#${closestParent.id}`
        )
        actLink.classList.add('active');
      }
    }
  });
}, {
  threshold: 0
});

window.addEventListener('DOMContentLoaded', () => {
  setTimeout( // Wait for images to fully load
    () => {
      mediaInViewport.forEach((item) => {
        observer.observe(item);
      });
    }, 1000);
});
* {
  margin: 0;
  padding: 0;
  font-family: sans-serif;
  font-size: 30px;
  text-decoration: none;
  color: inherit;
}

body {
  display: flex;
  cursor: default;
}

#left,
#right {
  width: 50%;
  height: 100vh;
  overflow-y: scroll;
  scroll-behavior: smooth;
}

#left {
  background-color: rgb(220, 220, 220);
}

#right {
  background-color: rgb(200, 200, 200);
}

.media {
  padding: 10px;
  padding-bottom: 0;
}

.media:nth-last-child(1) {
  margin-bottom: 10px;
}

img {
  display: block;
  width: 100%;
}

.link {
  cursor: pointer;
}

.active {
  background-color: black;
  color: white;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="left">
  <a class="link active" href="#landscape">Landscapes</a>
  <a class="link" href="#cats">Cats</a>
  <a class="link" href="#beer">Beer</a>
  <a class="link" href="#food">Food</a>
</div>
<div id="right">
  <section id="landscape">
    <div class="media">
      <img src="https://upload.wikimedia.org/wikipedia/commons/8/8d/Freudenberg_sg_Switzerland.jpg">
    </div>
    <div class="media">
      <img src="https://upload.wikimedia.org/wikipedia/commons/8/8d/Freudenberg_sg_Switzerland.jpg">
    </div>
  </section>
  <section id="cats">
    <article class="media">
      <img src="https://upload.wikimedia.org/wikipedia/commons/b/b8/Cute_cat_%281698598876%29.jpg">
    </article>
    <article class="media">
      <img src="https://upload.wikimedia.org/wikipedia/commons/b/b8/Cute_cat_%281698598876%29.jpg">
    </article>
  </section>
  <section id="beer">
    <article class="media beer">
      <img src="https://upload.wikimedia.org/wikipedia/commons/8/89/Craft_Beer_at_the_Taedonggang_Microbrewery_No._3_%2812329931855%29.jpg">
    </article>
  </section>
  <section id="food">
    <article class="media food">
      <img src="https://upload.wikimedia.org/wikipedia/commons/thumb/6/6d/Good_Food_Display_-_NCI_Visuals_Online.jpg/1200px-Good_Food_Display_-_NCI_Visuals_Online.jpg">
    </article>
  </section>
</div>

The observation function should work with all window sizes. Currently, if the window size is a bit higher, for example the "Beer" link doesn't receive the .active class if you click on it or scroll too insensitive.

Is there a way to fix that?

Rules like the following would be needed:

  1. Always only the last observed object should trigger an .active class.
  2. But: If there are at one point different observable objects visible (for example after a click on the link "Beer"), then the "most dominant" one (the one that is not cut off and most present) should trigger the .active class.

I would be sooooo thankful for help! <3

Anna_B
  • 820
  • 1
  • 4
  • 23

1 Answers1

2

const mediaInViewport = Array.from(document.querySelectorAll('.media'));
const sections = Array.from(document.querySelector('#right').children);
const links = Array.from(document.querySelectorAll('.link'));
let actLink = links[0];
let actSection = null;
let targetLink = null;
let targetSection = null;

document.body.addEventListener('click', (event) => {
  if (event.target.tagName === 'A') {
    event.preventDefault();
    targetLink = event.target;
    targetSection = sections.find(section => section.id ===  targetLink.href.slice(targetLink.href.lastIndexOf('#')+1))
    location.hash = targetLink.href.slice(targetLink.href.lastIndexOf('#'));
    actLink.classList.remove('active');
    actLink = links.find(l => event.target.href === l.href)
    actLink.classList.add('active');

  }
}, false)

observer = new IntersectionObserver((entries, observer) => {
  entries.forEach((entry) => {
    if (entry.target && entry.isIntersecting) {
      const closestParent = entry.target.closest('section');
      if (closestParent) {
        actLink.classList.remove('active');
        if (!targetLink) {
          actLink = links.find(link =>
            link.href.slice(link.href.lastIndexOf('#')) === `#${closestParent.id}`
          ) 
        } else {
          if (closestParent === targetSection){
            targetLink = null 
          }
        }
        actSection = sections.find(section => section.id ===  actLink.href.slice(actLink.href.lastIndexOf('#')))
        actLink.classList.add('active');
      }
    }
  });
}, {
  threshold: 0.3
});

window.addEventListener('DOMContentLoaded', () => {
  setTimeout( // Wait for images to fully load
    () => {
      mediaInViewport.forEach((item) => {
        observer.observe(item);
      });
    }, 1000);
});     
* {
  margin: 0;
  padding: 0;
  font-family: sans-serif;
  font-size: 30px;
}

body {
  display: flex;
  cursor: default;
}

#left,
#right {
  width: 50%;
  height: 100vh;
  overflow-y: scroll;
  scroll-behavior: smooth;
}

#left {
  background-color: rgb(220, 220, 220);
}

#right {
  background-color: rgb(200, 200, 200);
}

.media {
  padding: 10px;
  padding-bottom: 0;
}

.media:nth-last-child(1) {
  margin-bottom: 10px;
}

img {
  display: block;
  width: 100%;
}

.link:active {
  cursor: pointer;
}

a:target {
  background-color: black;
  color: white;
}

a {
  text-decoration: none;
  color: inherit;
}



section:target {
  background-color: black;
  color: white;
}


.active {
  background-color: black;
  color: white;
}   
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
    <div id="left">
      <a class="link active" href="#landscape">Landscapes</a>
      <a class="link" href="#cats">Cats</a>
      <a class="link" href="#beer">Beer</a>
      <a class="link" href="#food">Food</a>
    </div>
    <div id="right">
      <section id="landscape">
        <div class="media">
          <img src="https://upload.wikimedia.org/wikipedia/commons/8/8d/Freudenberg_sg_Switzerland.jpg">
        </div>
        <div class="media">
          <img src="https://upload.wikimedia.org/wikipedia/commons/8/8d/Freudenberg_sg_Switzerland.jpg">
        </div>
      </section>
      <section id="cats">
        <article class="media">
          <img src="https://upload.wikimedia.org/wikipedia/commons/b/b8/Cute_cat_%281698598876%29.jpg">
        </article>
        <article class="media">
          <img src="https://upload.wikimedia.org/wikipedia/commons/b/b8/Cute_cat_%281698598876%29.jpg">
        </article>
      </section>
      <section id="beer">
        <article class="media beer">
          <img src="https://upload.wikimedia.org/wikipedia/commons/8/89/Craft_Beer_at_the_Taedonggang_Microbrewery_No._3_%2812329931855%29.jpg">
        </article>
      </section>
      <section id="food">
        <article class="media food">
          <img src="https://upload.wikimedia.org/wikipedia/commons/thumb/6/6d/Good_Food_Display_-_NCI_Visuals_Online.jpg/1200px-Good_Food_Display_-_NCI_Visuals_Online.jpg">
        </article>
      </section>
    </div>
Daniil Loban
  • 4,165
  • 1
  • 14
  • 20
  • Thaaanks! I think it's better now. But if the window size is in a panorama-like orientation, and the images would be cut off anyways, it only works well if there is the setting "threshold: 0". In all other cases, "threshold: 0" would destroy the function. Do you have an idea to fix that? – Anna_B Feb 11 '21 at 07:12
  • No, I have not yet... But I'll think about it. – Daniil Loban Feb 11 '21 at 07:16
  • Maybe css with media queries will help,,, – Daniil Loban Feb 11 '21 at 07:31
  • Also you can change the threshold depend on orientation https://stackoverflow.com/questions/4917664/detect-viewport-orientation-if-orientation-is-portrait-display-alert-message-ad – Daniil Loban Feb 11 '21 at 07:36
  • I have some ideas to improve code, but I'll check it before – Daniil Loban Feb 11 '21 at 08:16
  • I added second snippet I think it works well (except maybe a strange scroll at the beginning of the section), and also find this fiddle http://jsfiddle.net/kunknown/GLLsv/2/ – Daniil Loban Feb 11 '21 at 10:26
  • Hey! Thanks! If I click there now for example "Food", and then "Landscapes", it often sticks at "Cats". There is no way to avoid that? – Anna_B Feb 11 '21 at 14:04
  • @Anna_B I removed other options as inappropriate. Now hashes don't change when scrolling but everything else works – Daniil Loban Feb 12 '21 at 01:54
  • It works!!! Thaaaank you sooooo much <3<3<3<3<3<3<3<3 – Anna_B Feb 12 '21 at 11:26