React에서 스크롤시 타이핑 효과내기

else·2023년 2월 11일
0

react

목록 보기
3/8

초기구상

//HomeSection6
/** @jsxImportSource @emotion/react */
import { css } from "@emotion/react";
import { useEffect, useRef, useState } from "react";
import { gsap } from "gsap/all";

const container = css`
  display: flex;
  justify-content: center;
  align-items: center;
  width: 100vw;
  height: 100vh;
  color: white;
  font-weight: 900;
  font-size: 2rem;
  background: linear-gradient(#f9fafb, black);
`;

function HomeSection6() {
  const innerText = "절차는 간단해요";
  const textArea = useRef();
  const [text, setText] = useState("");
  const [count, setCount] = useState(0);
  console.log("mount");
  const interval = () => {
    setInterval(() => {
      console.log(count);
      if (count === innerText.length) {
        clearInterval(interval);
        console.log("claer");
        return;
      }
      setText(text + innerText[count]);
      setCount(count + 1);
    }, 1000);
  };

  useEffect(() => {
    if (!textArea) return;

    gsap.to(".section6-text", {
      duration: 2,
      scrollTrigger: {
        trigger: ".section6-text",
        toggleActions: "restart none restart none",
        onEnter: () => interval(),
        start: "200 60%",
        end: () => "+=500",
        markers: true,
      },
    });

    return () => clearInterval(interval);
  });
  return (
    <div css={container} className="section6-text" ref={textArea}>
      <p>{text}</p>
    </div>
  );
}

export default HomeSection6;

  • 스크롤시 innerText를 변화시키는 함수를 실행한다.
  • 순차적으로 나오는 typing효과를 나타내기 위해 시간마다 함수를 반복실행시키는 setInterval함수를 사용한다.
  • gsap의 toggleAction 설정과 onEnter, onEnterBack을 사용해 해당 섹션에 들어올때마다 typing액션이 실행된다.
  • text를 한글자씩 늘리며 카운트를 센 뒤 카운트가 문자의 길이와 같아지면 clearInterval setInterval을 없애준다.

문제점

  • count의 값이 제대로 변하질 않으면서 난리난다.
  • clearInterval이 제대로 되지 않는 것 같다.
    • if 문 안의 길이가 찼을 때의 명령어를 새로 빼주어 그것을 언마운트될때 clearInterval로 해결

원인파악

  • count의 값이 바뀌면서 재렌더링된다.

  • 클로저(Closure)

    • 외부함수가 종료되었는데 내부함수가 계속 실행되는것 (자세한 것은 다른 포스팅에서)

    • setTimeout은 webApi이며 비동기이다. 따라서 callback queue에 쌓이고 call Stack이 비면 호출된다.

    • 렌더링 -> setTextsetCountcallback queue에 등록 후 종료

    • 이때의 count의 값은 렌더링 되면서 초기화된다.

    • 따라서 무한루프 (초기화 됐지만 2이상의 값이 나오는 것은 렌더링 된 상태에서 이전에 등록된 값이 겹쳐서 호출되기 때문)

해결 방안

  • redux에 등록하기

    • count값을 redux에서 불러옴으로써 초기화를 방지한다. (별로 안하고싶음)
  • 리렌더링이 되더라도 값을 유지시키기

    • useRef() 사용

      • useRef는 dom을 선택하는 용도도 있지만 값을 저장하는 용도로도 쓰인다.

      • 특히 값이 바뀌더라도 리렌더링되는 것을 방지해준다.

        let interval = useRef(null);
        useEffect(() => {
        if (!textArea) return;
        
        let intervalId;
        
        interval.current = () => {
          console.log(count);
          intervalId = setInterval(() => {
            if (count === innerText.length) {
              clearInterval(intervalId);
              return;
            }
            setText(text + innerText[count]);
            setCount(count + 1);
          }, 100);
        };
        
        gsap.to(".section6-text", {
          duration: 2,
          scrollTrigger: {
            trigger: ".section6-text",
            onEnter: () => interval.current(),
            start: "200 60%",
            end: () => "+=500",
            markers: true,
          },
        });
        
        return () => clearInterval(intervalId);
        });
    • interval.current에 callback을 저장하는 것으로 해결

UseRef

  • 해결하고 나니 누군가가 만든 custom hook 이 있다고한다.

  • 내부 코드를 살펴보니 useRef를 사용했는데 내 코드에는 변수만 바꾸면 되는것이라 그냥 이대로 사용하려고 한다.

profile
피아노 -> 개발자

0개의 댓글