如何使用 IntersectionObserver 使 React 组件在滚动时淡入淡出,但只有一次?

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

我试图在用户滚动时在 React 中为组件提供淡入效果,但我希望淡入效果仅在元素第一次移入视口时发生。

目前,每次元素移入视口时,我使用的代码都会导致淡入,因此它们会不断淡入和淡出。

这是我的淡入组件:

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>
  )
}

这些是我正在使用的样式:

.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; 
}

这是我的网站,它不断淡入淡出组件,提供糟糕的体验:

这是想要的效果:

怎样才能达到想要的效果?

这里有一个link代码沙箱来测试一下:Code sandbox link

如果entry.isIntersectingtrue,你只需要调用setVisible,所以只需替换:

setVisible(entry.isIntersecting);

有:

entry.isIntersecting && setVisible(true);

这样,一旦一个条目已经被标记为可见,它就不会被取消标记,即使你向上滚动,所以元素离开视口,entry.isIntersecting 变成 false 再次。

实际上,您甚至可以在那个时候调用 observer.unobserve,因为您不再关心了。

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.unobserve(domRef.current);
  }, []);

  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/react@16.12.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16.12.0/umd/react-dom.development.js"></script>

<div id="app"></div>