hillybob991
hillybob991

Reputation: 97

react-spring parallax - cannot register onScroll event when scrolling through parallax

I am using the Parallax component from react-spring to achieve a parallax effect on a parent component. Within this component, there are a bunch of background shapes that are within the parallax

    <Parallax ref={parallaxRef} pages={7.465} scrolling={props.stageData.contractActive && !isMobile ? true : false}>
       <div className="desktop-bg-shapes">
         <ParallaxLayer offset={0} speed={0.5}>
             <span className="rectangle bg-rectangle rectangle-1 orange"></span>
         </ParallaxLayer>
         <ParallaxLayer offset={0} speed={0.5}>
             <span className="rectangle bg-rectangle rectangle-2 orange"></span>
         </ParallaxLayer>
         <ParallaxLayer offset={0} speed={0.2}>
             <span className="rectangle bg-rectangle rectangle-3 orange"></span>
         </ParallaxLayer>
         <ParallaxLayer offset={1.3} speed={0.6}>
             <span className="rectangle bg-rectangle rectangle-4 light-blue"></span>
         </ParallaxLayer>
         <ParallaxLayer offset={4} speed={0.4}>
             <span className="rectangle bg-rectangle rectangle-5 blue"></span>
         </ParallaxLayer>
         <ParallaxLayer offset={6.7} speed={0.8}>
             <span className="rectangle bg-rectangle rectangle-6 light-blue"></span>
         </ParallaxLayer>
    </div>

And these are moving when I scroll through the main part of the page, which is content that is centred on the page (to the left and right are the above shapes that move when scrolling through this content) and is also within the parallax on the parent component

<ParallaxLayer ref={parallaxMainLayer} offset={0} speed={0}>
   <div className="viewport desktop">
       CONTENT HERE
....etc 

The parallax effect works. But another effect has stopped working. Previously when the user scrolled down the page, the header changed colour based on where the user was scrolling. Now however the onScroll event isn't firing and I am now sure how to get it to fire

Here is the header within the above parent component

<Header readCarefulStart={readCarefulStart} agreeSect={agreeSect}/>

I am passing the above ref to getBoundingClientRect() in the Header component itself. I am using useEffect to register the onScroll in the header component like so

    useEffect(() => {
        const handleScroll = (event) => {
                // fill info
                if (props.readCarefulStart.current.getBoundingClientRect().top > 150)
                    props.activeFillInfo()
                
                // read carefully
                else if (props.readCarefulStart.current.getBoundingClientRect().top <= 150 && props.readCarefulStart.current.getBoundingClientRect().top >= -3464 && props.agreeSect.current.getBoundingClientRect().top === 0) 
                    props.activeReadCarefully()
               
                // signature 
                else if (props.readCarefulStart.current.getBoundingClientRect().top < -3464) 
                    props.activeSignature() 
                
            })
        }

            window.addEventListener('scroll', handleScroll);

        return () => document.removeEventListener('scroll', handleScroll);

    }, [])

Before the header was changing colour based on onScroll but now this event doesn't register when I console.log(event). Why could this be?

function App(props){
  return (
   <Header readCarefulStart={readCarefulStart} agreeSect={agreeSect}/>

   <Parallax ref={parallaxRef} pages={7.465} scrolling={props.stageData.contractActive && !isMobile ? true : false}>
       <div className="desktop-bg-shapes">
         <ParallaxLayer offset={0} speed={0.5}>
             <span className="rectangle bg-rectangle rectangle-1 orange"></span>
         </ParallaxLayer>
         <ParallaxLayer offset={0} speed={0.5}>
             <span className="rectangle bg-rectangle rectangle-2 orange"></span>
         </ParallaxLayer>
         <ParallaxLayer offset={0} speed={0.2}>
             <span className="rectangle bg-rectangle rectangle-3 orange"></span>
         </ParallaxLayer>
         <ParallaxLayer offset={1.3} speed={0.6}>
             <span className="rectangle bg-rectangle rectangle-4 light-blue"></span>
         </ParallaxLayer>
         <ParallaxLayer offset={4} speed={0.4}>
             <span className="rectangle bg-rectangle rectangle-5 blue"></span>
         </ParallaxLayer>
         <ParallaxLayer offset={6.7} speed={0.8}>
             <span className="rectangle bg-rectangle rectangle-6 light-blue"></span>
         </ParallaxLayer>
      </div>

      <ParallaxLayer ref={parallaxMainLayer} offset={0} speed={0}>
        <div className="viewport desktop">
          CONTENT HERE
          ....etc 
        </div>
      </ParallaxLayer>
   </Parallax>
   )
}

function Header(props) {
useEffect(() => {
        const handleScroll = (event) => {
                // fill info
                if (props.readCarefulStart.current.getBoundingClientRect().top > 150)
                    props.activeFillInfo()

                // read carefully
                else if (props.readCarefulStart.current.getBoundingClientRect().top <= 150 && props.readCarefulStart.current.getBoundingClientRect().top >= -3464 && props.agreeSect.current.getBoundingClientRect().top === 0) 
                    props.activeReadCarefully()

                // signature 
                else if (props.readCarefulStart.current.getBoundingClientRect().top < -3464) 
                    props.activeSignature() 

            })
        }

            window.addEventListener('scroll', handleScroll);

        return () => document.removeEventListener('scroll', handleScroll);

    }, [])

return(
        <header>
            <div className="viewport">
                <div className="inner">
                    <nav>
                        <ul>
                              <li className='start-header'><span className="num-circle">1</span><span className="text">Start</span></li>        
                              <li className='fill-info'><span className="num-circle">2</span><span className="text">Fill in information</span></li>                    
                              <li className='read-carefully'><span className="num-circle">3</span><span className="text">Read Carefully</span></li>                   
                              <li className='sign'><span className="num-circle">4</span><span className="text">Sign</span></li>
                              <li className='agreement'><span className="num-circle">5</span><span className="text">New agreement</span></li>     
                        </ul>
                    </nav>
                </div>
            </div>
        </header>
)
}

*edit added all code in one section at the end

Upvotes: 0

Views: 3168

Answers (4)

Marvin
Marvin

Reputation: 754

According to its react-spring documentation,

The Parallax component creates a scrollable container in which ParallaxLayers can be placed or React.Fragments whose only direct children are ParallaxLayers. Because Parallax is a scrollable container all scroll events are fired from the container itself therefore, listening for scroll on window won't work. However, if you want to attach additional events you can use ref.current.container

and after I follow its steps, here's what I get

const ref = useRef<IParallax>();

const scrollListener = () => {
  const handleWheelEvent = () => {
    const {container, current} = ref.current;
    const scrollpercent = current / (container.current.scrollHeight - window.innerHeight);
  };
  window.addEventListener('wheel', handleWheelEvent);
  return () => {
    window.removeEventListener('wheel', handleWheelEvent);
  };
};
useEffect(scrollListener, []);

return (
  <Parallax className={styles['parallax-pane']} pages={5} ref={ref}>
    ...

Upvotes: 2

Giorgi Sharashenidze
Giorgi Sharashenidze

Reputation: 21

This worked for me here:

const parallaxRef = useRef<IParallax>(null)

function scrollListener() {
        const container = parallaxRef.current?.container
            .current as HTMLDivElement

        container.onscroll = () => {
            setScrollTop(parallaxRef.current?.current as number)
        }
        return () => {
            container.onscroll = null
        }
}


useEffect(scrollListener, [])

Marvin's solution unfortunately does not work on mobile devices.

Upvotes: 1

Nicola D&#39;Aniello
Nicola D&#39;Aniello

Reputation: 43

A more neat solution would be to set the listener directly from the ref of the Parallax component, rather then using querySelector.

Example:

const Test = () => {
  const parallaxRef = useRef()

  const onScroll = () =>
    console.log(parallaxRef.current.current / parallaxRef.current.space)

  useEffect(() => {
    if (!parallaxRef.current || !parallaxRef.current.container) return
    parallaxRef.current.container.onscroll = onScroll
  })

  return (
    <div>
      <Parallax pages={3} ref={parallaxRef}>
        <ParallaxLayer offset={0} speed={0.5}>
          <span onClick={() => parallaxRef.current.scrollTo(1)}>
            Layers can contain anything
          </span>
        </ParallaxLayer>
        ...
      </Parallax>
    </div>
  )
}

As you can see from the source of react-spring Parallax, the ref contains references to the two div elements created by the component.

Upvotes: 3

hillybob991
hillybob991

Reputation: 97

I fixed this myself

So the issue was that I was adding an event listener to the window as opposed to the parallax

window.addEventListener('scroll', handleScroll);

So I added a class to the main parallax component. There are two div's created by the parallax at the top level so you need to target two levels above (the grandparent) the immediate child to the parallax in your code

useEffect(() => {
    document.querySelector('div.classNameOfChildOfParallax').parentElement.classList.add('parallax-child')
    document.querySelector('div.parallax-child').parentElement.classList.add('parallax-main')
}, []) 

Then in Header you do this

document.querySelector('div.parallax-main').addEventListener('scroll', handleScroll);

Upvotes: -2

Related Questions