image

FullPage 스크롤 시 FadeInOut

태그
ReactJavascript
상세설명FullPage 스크롤 시 FadeInOut
작성일자2024.06.10

저번 글에 이어서 휠 이벤트(= 스크롤) 시 화면이 fadeInOut 이 되면서 페이지가 전환되게 구현해보았다.

구현 포인트

  • 마우스 휠 사용 시 fadeInOut 이 되면서 한 페이지 씩 넘어가기
  • handleScrollVertical 함수

  • handleScrollVertical함수는 useCallback을 통해 메모이제이션되므로, 동일한 함수 참조가 유지된다. ( useCallback을 사용 안 하면 컴포넌트가 리렌더링 될 때마다 handleScrollVertical 함수가 새로 생성된다. useEffect 내부의 클린업 함수가 이전 함수와 같은 참조를 가지지 않기 때문에 이벤트 리스너를 정확히 제거하지 못 할 수 있다.)
  • event.deltaY값을 사용하여 사용자가 스크롤 하는 방향을 결정합니다 (event.deltaY가 양수면 아래로 스크롤, 음수면 위로 스크롤).
  • setVisibleIndex를 호출하여 visibleIndex 상태를 업데이트합니다. 이때 newIndex가 0과 2 사이의 값으로 제한한다. (총 갯수에서 - 1)
  • const handleScrollVertical = useCallback(
      (event) => {
        event.preventDefault();
    
        const scrollAmount = event.deltaY > 0 ? 1 : -1;
        setVisibleIndex((prevIndex) => {
          const newIndex = Math.max(0, Math.min(prevIndex + scrollAmount, 2));  // 3개의 항목(인덱스 0~2)
          return newIndex;
        });
      },
      [setVisibleIndex]
    );

    스크롤 실행

  • useEffect를 사용하여 컴포넌트가 마운트될 때 wheel 이벤트 리스너를 추가하고, 언마운트될 때 이벤트 리스너를 제거합니다.
  • passive: false 옵션을 사용하여 event.preventDefault()를 호출할 수 있도록 한다.
  • { passive: false } 옵션은 이벤트 리스너에 전달되는 설정 객체의 일부로, 브라우저가 해당 이벤트 리스너에서 event.preventDefault() 호출할 수 있어 스크롤 이벤트에서 중요하다.

    여기 코드에서 passive: false를 사용하는 이유 handleScrollVertical 함수에서 event.preventDefault()를 호출하기 때문이다. handleScrollVertical 함수의 event.preventDefault()를 통해 기본 스크롤 동작을 막고, 대신 커스텀 스크롤 동작을 구현하려는 것이다. passive: false가 없으면 event.preventDefault()가 호출되더라도 기본 스크롤 동작을 막지 못할 수 있다.

    useEffect(() => {
      const horizontalDiv = horizontalDivRef.current;
      if (horizontalDiv) {
        horizontalDiv.addEventListener('wheel', handleScrollVertical, { passive: false });
      }
      return () => {
        if (horizontalDiv) {
          horizontalDiv.removeEventListener('wheel', handleScrollVertical);
        }
      };
    }, []);

    FadeInOut 효과

  • section(fade_container) 요소에 horizontalDivRef를 연결하여 useRef가 해당 DOM 요소를 참조할 수 있게 한다.
  • div 요소의 style 속성을 활용하여 면 전체를 덮도록 설정합니다.
  • <section ref={horizontalDivRef} className="fade_container">
         {[1, 2, 3].map((item, index) => (
              <div
                key={index}
                className={`fade_wrap ${visibleIndex === index ? 'visible' : 'invisible'}`}
                style={{
                  position: 'absolute',
                  top: 0,
                  left: 0,
                  width: '100vw',
                  height: '100vh',
                  transition: 'opacity 0.5s',
                  opacity: visibleIndex === index ? 1 : 0,
                  backgroundColor:
                    index === 0 ? 'pink' : index === 1 ? 'purple' : 'green',
                }}
              >
                {item}
              </div>
        ))}
    </section>

    .fade_container {
      @apply overflow-hidden whitespace-nowrap relative w-screen h-screen;
    }
    .visible {
      opacity: 1;
    }
    .invisible {
      opacity: 0;
    }

    전체 코드

    
    import { useCallback, useRef, useState, useEffect } from 'react'
    
    export default function Test() {
      const horizontalDivRef = useRef(null)
      const [visibleIndex, setVisibleIndex] = useState(0)
    
      const handleScrollFade = useCallback(
        (event) => {
          event.preventDefault()
    
          const scrollAmount = event.deltaY > 0 ? 1 : -1
          setVisibleIndex((prevIndex) => {
            const newIndex = Math.max(0, Math.min(prevIndex + scrollAmount, 2)) // 3개의 항목(인덱스 0~5)
            return newIndex
          })
        },
        [setVisibleIndex],
      )
    
      useEffect(() => {
        const horizontalDiv = horizontalDivRef.current
        if (horizontalDiv) {
          horizontalDiv.addEventListener('wheel', handleScrollFade, {
            passive: false,
          })
        }
        return () => {
          if (horizontalDiv) {
            horizontalDiv.removeEventListener('wheel', handleScrollFade)
          }
        }
      }, [])
    
      return (
        <>
          <section ref={horizontalDivRef} className="fade_container">
            {[1, 2, 3].map((item, index) => (
              <div
                key={index}
                className={`fade_wrap ${visibleIndex === index ? 'visible' : 'invisible'}`}
                style={{
                  position: 'absolute',
                  top: 0,
                  left: 0,
                  width: '100vw',
                  height: '100vh',
                  transition: 'opacity 0.5s',
                  opacity: visibleIndex === index ? 1 : 0,
                  backgroundColor:
                    index === 0 ? 'pink' : index === 1 ? 'purple' : 'green',
                }}
              >
                {item}
              </div>
            ))}
          </section>
        </>
      )
    }
    
    

    결과