본문 바로가기
[Study] 개발 공부/[React] 리액트 공부

[React] 스크롤에 따라 텍스트가 나타나는 효과 구현하기

by 지공A 2024. 11. 6.

스크롤에 따라 텍스트가 나타나는 효과 구현하기

(Feat. getBoundingClientRect, useRef)

 

 

 

1. 화면에 보여줄 텍스트 라인을 보여줄 상태 생성하기

[
      "첫 번째 문장",
      "두 번째 문장",
      "세 번째 문장",
      "네 번째 문장",
      "다섯 번째 문장",
    ]

 

- 각 문장들을 배열에 저장한다.

  // 스크롤 위치에서 보일 텍스트 index를 저장할 배열
  const [visibleLines, setVisibleLines] = useState<number[]>([]);

 

- 현재 스크롤이 위치한 화면에서 보여줄 텍스트의 index를 저장할 number 빈 배열을 만들어 준다.

- 예를 들어, 두 번째 문장 까지 보여준다면 [0, 1] 이 저장될 것이다.

 

2. useRef를 사용하여 이 텍스트를 보여주는 컴포넌트(요소)를 저장한다.

  const textRef = useRef<HTMLDivElement>(null); // Text div에 접근할 Ref

 

- 나는 <Text> div 컴포넌트에 각 라인들을 <TextLine> 으로 보여주고 있으므로 해당 div 컴포넌트에 Ref 를 지정해줬다.

 

3. 스크롤 이벤트 함수 만들어주기

 //   스크롤 위치에 따라 한 줄씩 텍스트라인이 보이게 하는 함수
  const handleScroll = () => {
    if (textRef.current) {
      // Text div가 top 에서 얼마나 떨어져 있는지, 스크롤 할 때마다 변경
      const { top } = textRef.current.getBoundingClientRect();
      // 화면 상단 90%를 지정. 즉, 화면 하단 10% 지점에서부터 텍스트가 나타나게끔. 화면의 높이 기준이므로 고정
      const scrollThreshold = window.innerHeight * 0.9;

      //   화면에 보일 텍스트라인을 저장할 배열
      const newVisibleLines = [];
      //   현재 스크롤 한 시점의 top이 지정한 높이를 넘었으면 배열에 텍스트라인을 넣어준다.
      //   한 줄당 56px정도 차를 주어 스크롤을 내릴 때 마다 한 줄씩 나타나는 느낌을 주도록 한다.
      for (let i = 0; i < introText.desc.length; i++) {
        if (top + i * 56 < scrollThreshold) {
          newVisibleLines.push(i);
        }
      }
      setVisibleLines(newVisibleLines);
    }
  };

 

- textRef.current.getBoundingClientRect() 함수는 지정한 요소의 현재 위치를 가져오는 함수이다.

- 그 중, top 요소만 사용할 것이기 때문에 top 변수를 저장해줬다.

- 그 후 스크롤 시점에 따라 visibleLines 상태 배열에 보여줄 텍스트 라인의 index를 저장해준다.

- 각 줄 마다 보여야 할 위치가 다르기 때문에 56px 정도 차를 주었다. 

- 56px의 기준은 직접 스크롤 해보고, 가장 자연스러운 px을 지정해주었다.

 

4. 스크롤 이벤트 설정(useEffect)

 //   스크롤 할 때마다 handleScroll 함수 실행
  useEffect(() => {
    window.addEventListener("scroll", handleScroll);
    // 언마운트 시 리스너 제거는 꼭 해주자
    return () => window.removeEventListener("scroll", handleScroll);
  }, []);

 

- 스크롤 할 때마다, 위의 이벤트 함수를 실행해서 보여줄 텍스트 라인을 결정한다.

- 즉, 스크롤을 할 때마다, 현재 화면의 top 위치를 찾고, 이 위치와 내가 지정한 위치를 비교해서 몇 개의 텍스트 라인을 보여줄 지 결정한다!

 

5. 각 텍스트 라인을 순차적으로 보이게 하기

	<Text className="text" ref={textRef}>
          {introText.desc.map((line, index) => (
            <TextLine key={index} isVisible={visibleLines.includes(index)}>
              {line}
            </TextLine>
          ))}

 

- 각 TextLine 컴포넌트를 만들어주고, visibleLines의 index에 TextLine의 index가 존재한다는 의미는 해당 TextLine을 보여준다는 의미이다.

- 예를 들어, 아까 두 번째 문장까지 보여준다면 visibleLines에 [0, 1]이 저장될 것이고, TextLine의 0, 1 index에 해당하는 두 번째 줄 까지만 isVisible이 true라는 의미이다!

 

const TextLine = styled.p<{ isVisible: boolean }>`
  font-size: 16px;
  opacity: 0; // 처음에는 보이지 않도록 설정
  /* isVisible일 때에만 fallDown 애니메이션 적용 */
  animation: ${(props) => props.isVisible && fallDown} 0.6s ease forwards;
`;

// 위에서 아래로 내려오는 애니메이션
const fallDown = keyframes`
  from {
    opacity: 0;
    transform: translateY(-16px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
`;

 

 - 따라서, isVisible이 true일 때에만 fallDown 애니메이션을 실행해 주면 된다.