[프로젝트] 랜딩 페이지 구현

Jade·2023년 3월 1일
1

프로젝트

목록 보기
22/28

부제 : 근데 이제 언제나와 같이 삽질을 곁들인...

구현된 랜딩 페이지


intersectionObserver

Intersection Observer API 는 그들이 감시하고자 하는 요소가 다른 요소(viewport)에 들어가거나 나갈때 또는 요청한 부분만큼 두 요소의 교차부분이 변경될 때 마다 실행될 콜백 함수를 등록할 수 있게 합니다. 즉, 사이트는 요소의 교차를 지켜보기 위해 메인 스레드를 사용할 필요가 없어지고 브라우저는 원하는 대로 교차 영역 관리를 최적화 할 수 있습니다. (출저: MDN)

이미 무한 스크롤을 만들 때 사용하는 useScroll이라는 훅에서 intersectionOnserver를 사용하고 있었기 때문에 해당 내용까지 참고해서 구현을 시작했다.

그런데 문제가 생겼다. 우리는 이번 프로젝트에서 postcss를 사용했고, index.css, App.css를 제외한 파일들은 모두 모듈화되어 있었다.

예를 들어 className={styles.title}와 같이 클래스명을 지정해준다고 하더라도 모듈화 되는 과정에서 클래스명이 class="Home_title__qAdt+"이런식으로 변화되는 것을 개발자 도구의 요소창에서 확인할 수 있다.

검색해보면 보통 자바스크립트로 감시하고자 하는 요소들을 선택해줄 때는 querySelectorAll를 사용하는 것 같았고, 리액트의 경우에는 useRef를 사용하는 것 같았다. querySelector는 일단 추적하고자 하는 클래스명을 요소들에 입력해주더라도 모듈화 되면 제대로 추적하지 못할 것 같아서 사용하지 않기로 했고, useRef를 사용해야 했다. 감지해야할 요소가 여러개였기 때문에 useRef를 사용하는 데에도 변형이 필요했다.

const Home = () => {
	const elementRef = useRef([]);
  
  //중략
  
  return (
    //중략
   <article className={`${styles.searchTitle}`}
            ref={(el) => (elementRef.current[0] = el)} >
  //중략
	<div className={`${styles.mock} ${styles.leftUp} `}
         ref={(el) => (elementRef.current[1] = el)} >
  )

useRef를 여러 개 만드는 게 아니라, 하나의 Ref에 여러 개의 돔 주소들을 저장할 수 있는 방법은 위와 같다. 초기값을 null이 아닌 빈 배열로 두고, 주소값을 저장할 요소들에 각각 ref 속성을 주는데, 바로 elementRef를 할당하는 것이 아니라 콜백 함수를 통해서 배열 속에 차례로 넣어주는 방식이다.

이렇게 주소값들을 하나의 Ref에 저장해주는 것까지는 순조로웠는데, 문제는 다음이었다. 아래와 값이 intersectionObserver의 콜백으로 들어갈 함수 handleScroll을 아래와 같이 작성해주려고 했는데, CSSS 모듈화 때문인지 의도한 대로 'active' 클래스가 부여되지 않는 것이었다.

const handleScroll = (entries) => {
  entries.forEach((entry)=>{
    if (entry.isIntersecting) entry.target.classList.add("active");
    else entry.target.classList.remove("active");
  }
   })
};


useEffect(() => {
    const observer = new IntersectionObserver(handleScroll, {
      threshold: 0.25,
    });

    elementRef.current.forEach((el) => {
      observer.observe(el);
    });

    return () => observer?.disconnect();
  }, []);


삽질 (뛰어넘어도 되는 부분)

  1. 모듈화 하지 않은 css 파일을 만들어서 active 관련 부분만 몰아넣기
    => 문제가 css 모듈에서 비롯된 것이므로 사실 제일 직관적인 방법이었으나, 생각보다 active만 따로 빼내서 css 파일을 만드는 게 쉬울 것 같지는 않았다.
    => 탈락.

  2. class를 직접 부여하는 것이 아닌 isActive라는 상태를 만들어서 isAcive가 true일 때 styles.active 클래스가 요소 내에서 부여되게 만들자. 감지할 요소들이 여러개이기 때문에 isActive 를 객체나 배열로 설정해야 함.
    => 이 방법은 그래서 entries의 각 요소들을 감지할 때마다 이 요소들이 배열의 몇번째 요소인지 어떻게 알 건데! 가 핵심이었는데, 함께 고민하던 팀원이 콘솔을 찍어보다가 entry의 target이 elementRef의 돔 요소와 결국 동일하다는 것을 발견했고, entries.forEach 내부에서 elementRef.current.forEach를 한 번 더 돌려서 각 요소가 entry.target이 동일한 경우 isActive의 해당하는 인덱스에 entry.isIntersecting (불리언값) 을 할당하도록 만들었다.

결과적으로 2번 방법을 통해서 랜딩 애니메이션이 작동했지만, 찜찜함을 지울 수가 없었다. 왜냐? 반복문이 두 번이나 들어가잖아!!!!!!! 😱

//감지할 요소가 10개이므로 배열에도 10개의 false가 초기값으로 설정 필요 
const [isActive, setIsActive] = useState([false,false,false,false,false,false,false,false,false,false])

const handleScroll = (entries) => {
    entries.forEach((entry) => {
      elementRef.current.forEach((el, idx) => {
        if (el === entry.target)
          setIsActive((prev) => {
            const clone = prev.slice();
            clone[idx] = entry.isIntersecting;
            return clone;
          });
      });
    });
  };

useEffect(() => {
    const observer = new IntersectionObserver(handleScroll, {
      threshold: 0.25,
    });

    elementRef.current.forEach((el) => {
      observer.observe(el);
    });

    return () => observer?.disconnect();
  }, []);

결론

onst Home = () => {
  const navigate = useNavigate();
  const elementRef = useRef([]);

  const handleScroll = ([entry]) => {
    if (entry.isIntersecting) entry.target.classList.add(`${styles.active}`);
    else entry.target.classList.remove(`${styles.active}`);
  };

  useEffect(() => {
    const observer = new IntersectionObserver(handleScroll, {
      threshold: 0.25,
    });

    elementRef.current.forEach((el) => {
      observer.observe(el);
    });

    return () => observer?.disconnect();
  }, []);

return (//생략);
  • 템플릿 리터럴, Jsx문법을 사용해서 classList에 add와 remove를 해주는 게 정답이었다. 'styles.active'처럼 문자열 형식으로는 넣어봤는데 jsx 문법을 사용할 생각을 못 했다... 😂 어제 내내 이거 해결한다고 붙잡고 있었는데 조금 허무하고 조금 후련했다...

  • 잘은 모르겠지만 콘솔창에 맨 처음 entries를 찍었을 때는 감시할 모든 요소들이 다 배열속에 있는데, 스크롤을 내리면서 보면 지금 감시하고 있는 한 개의 요소만 담긴 배열이 entries가 된다. 그러므로 구조분해할당으로 entry만 꺼내오면 forEach문 안 써도 됨.

이렇게 여러개의 entry들이 담겨있다가

스크롤을 하면 한 개씩 담긴 배열이 나타난다
profile
키보드로 그려내는 일

0개의 댓글