Javascript

FullPage 스크롤 시 FadeInOut

minsun309 2024. 10. 13. 16:37

저번 글에 이어서 휠 이벤트(= 스크롤) 시 화면이 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>
    </>
  )
}

 

 

결과