Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to make a React component fade in on scroll using IntersectionObserver, but only once?

I am trying to give components a fade-in effect in React when the user scrolls, but I want the fade-in effect to only happen the first time the element moves into the viewport.

Currently, the code I am using causes a fade-in every time the element moves into the viewport, so they are constantly fading in and out.

Here is my fade-in component:

import React, {useState, useRef, useEffect} from 'react';
import './styles/FadeInSection.css';

export default function FadeInSection(props) {
  const [isVisible, setVisible] = useState(true);

  const domRef = React.useRef();

  useEffect(() => {
    const observer = new IntersectionObserver(entries => {
      entries.forEach(entry => setVisible(entry.isIntersecting));
    });

    observer.observe(domRef.current);

    return () => observer.unobserve(domRef.current);
  }, []);

  return (
    <div ref={ domRef } className={ `fade-in-section ${ isVisible ? 'is-visible' : '' }` }>
      { props.children }
    </div>
  )
}

And these are the styles I'm using:

.fade-in-section {
  opacity: 0;
  transform: translateY(20vh);
  isibility: hidden;
  transition: opacity 0.2s ease-out, transform 0.6s ease-out;
  will-change: opacity, visibility;
}

.fade-in-section.is-visible {
  opacity: 1;
  transform: none;
  visibility: visible;
  display: flex; 
}

Here is my website, which keeps fading components in and out, offering a terrible experience:

My Website

And this is the desired effect:

Sweet fade-in effect

How can I achieve the desired effect?

Here is a link to the code sandbox to test it: Code sandbox link

like image 912
klaurtar1 Avatar asked Jan 04 '20 23:01

klaurtar1


1 Answers

You only need to call setVisible if entry.isIntersecting is true, so simply replace:

setVisible(entry.isIntersecting);

With:

entry.isIntersecting && setVisible(true);

This way, once an entry has already been marked as visible, it won't be unmarked, even if you scroll back up, so the element goes out of the viewport, and entry.isIntersecting becomes false again.

Actually, you can even call observer.unobserve at that point, as you don't care anymore.

const FadeInSection = ({
  children,
}) => {
  const domRef = React.useRef();
  
  const [isVisible, setVisible] = React.useState(false);

  React.useEffect(() => {
    const observer = new IntersectionObserver(entries => {
      // In your case there's only one element to observe:     
      if (entries[0].isIntersecting) {
      
        // Not possible to set it back to false like this:
        setVisible(true);
        
        // No need to keep observing:
        observer.unobserve(domRef.current);
      }
    });
    
    observer.observe(domRef.current);
    
    return () => observer.disconnect();
  }, []);

  return (<section ref={ domRef } className={ isVisible ? ' is-visible' : '' }>{ children }</section>);
};

const App = () => {  
  const items = [1, 2, 3, 4, 5, 6, 7, 8].map(number => (
    <FadeInSection key={ number }>Section { number }</FadeInSection>
  ));

  return (<main>{ items }</main>);
}

ReactDOM.render(<App />, document.querySelector('#app'));
body {
  font-family: monospace;
  margin: 0;
}

section {
  padding: 16px;
  margin: 16px;
  box-shadow: 0 0 8px rgba(0, 0, 0, .125);
  height: 64px;
  opacity: 0;
  transform: translate(0, 50%);
  visibility: hidden;
  transition: opacity 300ms ease-out, transform 300ms ease-out;
  will-change: opacity, visibility;
}

.is-visible {
  opacity: 1;
  transform: none;
  visibility: visible;
  display: flex; 
}
<script src="https://unpkg.com/[email protected]/umd/react.development.js"></script>
<script src="https://unpkg.com/[email protected]/umd/react-dom.development.js"></script>

<div id="app"></div>
like image 118
Danziger Avatar answered Oct 24 '22 21:10

Danziger