1

I have a SPA with a long height page, where one section represents one page. Actual code:

const ScrollWrapper = () => {
  return (
  <div className={styles.ScrollWrapper + ` ` + 'snap-container'}>
    <div className={styles['navigation-fix-wrapper']}>
      <ScrollNavigation/>
    </div>
    <Home/>
    <Projects/>
    <Media/>
    <Concerts/>
    <Contacts/>
  </div>
)};

A have a navigation component at the top. It's a bunch of anchors with list items inside it, every anchor links to a sections like with scrolling effect, no page changes. On click styling of an clicked link changes to active.

Goal: implement IntersectionObserver to all of my sections (from Home to Contacts) and depending on which component is in viewport the active link in ScrollNavigation component will be changed (if I scroll from Home to Concerts, the Concerts link will be active)

I have absolute zero ideas how can I do it, but I'm pretty sure that there are some ways.

I think of something like (pseudocode):

 const [activeLink, setActiveLink] = useState('home')
 //some code here
 if (IntersectionObserver.state === 'home' {
   setActiveLink('home')
 } else if (IntersectionObserver.state === 'concerts' {
   setActiveLink('concerts')
 }
 //some code here
 return (
  <div className={styles.ScrollWrapper + ` ` + 'snap-container'}> 
    <ScrollNavigation active={activeLink} />
  //some code here
  </div>
 )

UPDATE:

Added this code to ScrollWrapper, as kritiz suggested. IntersectionObserver fires console.log only if parent component is in if condition, but not the children. What I'm doing wrong?

  useEffect(() => {
    var observer = new IntersectionObserver(onIntersection, {
      root: null,   // default is the viewport
      threshold: .5 // percentage of taregt's visible area. Triggers "onIntersection"
    })
    
    // callback is called on intersection change
    function onIntersection(entries, opts){
      entries.forEach(entry => {
        if (entry.target.id === 'projects') {
          console.log('projects')
        } 
      }  
  
      )
    }
    observer.observe( document.querySelector('.snap-container') )

  }, [])

Changed target to currentTarget, error occures:

Cannot read properties of undefined (reading id) 
  • does this help https://stackoverflow.com/questions/487073/how-to-check-if-element-is-visible-after-scrolling – Kritish Bhattarai Apr 03 '22 at 16:41
  • @kritiz I added a partially working solution from your link, thank you for that, but this is not actually what I'm looking for. So IntersectionObserver works with parent component ScrollWrapper, but for some reason it does not recognizes child elements. I suppose I'm doing something wrong – mortvicious Apr 03 '22 at 16:55
  • if your goal is to style nav-link based on scroll event , you can use "scroll" event listener and check which element is currently visible as "scroll" event occurs and setActiveLink() based on that – Kritish Bhattarai Apr 04 '22 at 00:51

1 Answers1

1

As kritiz suggested, I used a scroll event listener on window and it actually worked. It still sounds like memory leaks, and I will continue to try implementing the IntersectionObserver, but it works as I needed.

Code:

  useEffect(() => {
    
    window.addEventListener('scroll', function() {
      
      const observable = {
        home: document.querySelector('.home'),
        projects: document.querySelector('.projects'),
        media: document.querySelector('.media')
  
      }
      const pos = {
        home: observable.home.getBoundingClientRect(),
        projects: observable.projects.getBoundingClientRect(),
        media: observable.media.getBoundingClientRect()
      }
      

      if (pos.home.top < window.innerHeight && pos.home.bottom >= 0) {
        setActiveLink('home')
      } else if (pos.projects.top < window.innerHeight && pos.projects.bottom >= 0) {
        setActiveLink('projects')
      } else if (pos.media.top < window.innerHeight && pos.media.bottom >= 0) {
        setActiveLink('media')
      }
    })

    return () => {
      
    }
  }, [])

UPDATE:

Second approach is to use react-scroll library, which works like a charm and does everything I need.