scroll 이벤트 (feat.프로젝트)

📅 TIL #142




🎯 Achievement Goals

  1. addEventListener를 활용한 scroll 이벤트
  2. intersectionObserver API를 활용한 scroll 이벤트







Intro.


프로젝트를 하면서 랜딩페이지를 전반적으로 담당했던 나는, scroll 이벤트를 정말 많이 활용했다. 랜딩페이지에는 웹 서비스에 대한 설명이 주로 담겨 있는데, 딱딱하게 설명하면 지루함을 느낄 수가 있기 때문에 이벤트 요소를 넣어가며 사용자에게 재밌게 설명해주고 싶었다.


scroll 이벤트를 addEventListener와 intersectionObserver API을 활용해서 구현하였고, 어떤식으로 구현했는지와 개선 사항이 필요한 부분을 블로깅해보려고 한다. 그리고 리팩토링을 한다면 어떤 방향으로 하고 싶은지까지 추가적으로 적어보려고 한다.





addEventListener를 활용한 scroll 이벤트


addEventListener를 통해 정말 다양한 이벤트를 구현할 수 있지만 오늘은 첫번째 인자에 scroll을 넣어서 내가 스크롤 이벤트를 활용해서 어떻게 구현했는지와 이에 대한 문제점을 설명하려고 한다.


문제점의 결론부터 말하자면 프로젝트를 했을 때에 당시, 나는 addEventListener로 scroll이벤트를 구현하면 성능상에 큰 문제가 될 것이라고 깨닫지 못하였다. 애초에 그런 지식을 가지고 있지 못했던 것 같다.
(이 문제를 깨달은 것은 프로젝트가 끝나고 따로 공부를 더 하면서 알게 된 지식이다.)


addEventListener의 scroll로 이벤트를 구현을 하면 단시간에 수백번, 수천번 호출될 우려가 있고 동기적으로 실행되기 때문에 이에 따라 메인 스레드(Main Thread)에 영향을 준다. 그래서 실제로 내가 addEventListener로 구현한 부분을 개발자 도구 performance 탭을 활용해 보았다.


addEvent


정말 놀랍게도 addEventListener로 구현한 구간에만 프레임이 빨간색으로 표시가 된 것을 볼 수가 있다.
(이 구간을 제외한 모든 부분은 intersectionObserver API로 이벤트를 구현했다.)


나중에 알았지만 MDN에서도 DOM수정과 같이 느린 작업보다는 requestAnimationFrame()이나 쓰로틀(throttle)을 사용하는 것을 권장한다고 한다……


addEventScroll


현재 랜딩페이지의 이 부분이 리팩토링이 필요한 부분이고, 리팩토링을 하게 된다면 React Hook으로 만들어 놓았던 intersectionObserver의 함수를 가져다가 쓸 것으로 예상된다.


지금 당장 리팩토링을 하지 않는 이유는 기본기를 다지기 위해 JS로 이벤트 기능들을 구현하고 있으며, 나의 실수를 조금 더 박제해두고 싶었다.. 이걸 보면서 많은 것을 깨달았기 때문이다.






intersectionObserver API를 활용한 scroll 이벤트


이 비동기 API를 처음 사용했을 때는 신세계였다. 많은 웹 사이트에서 보던 Fade-in/out 기능들은 이 함수를 통해 구현이 되었다고 직감적으로 깨달았고, 무한 스크롤기능을 구현할 때에도 이 함수를 활용했다.


intersectionObserver는 addEventListener와 다르게 비동기적으로 실행이 되며, 요소를 관찰 대상으로 정하고, 요소가 view port에 나타났을 경우에만 실행을 시킨다. 그렇기 때문에 메인 스레드에 영향이 가지 않는다.


더 확실한 차이는 reflow를 발생시키지 않는데, 실제로 내 프로젝트에서 intersectionObserver와 addEventListener의 애니메이션 실행 구간에서 performance 탭을 활용해서 reflow 현상을 테스트 해 보았다.


reflow



위 사진은 addEventListener를 활용해서 스크롤 이벤트를 구현한 구간이다. 확실히 Reflow가 많이 일어나는 것을 볼 수 있다.




reflow2


위 사진은 intersectionObserver를 활용한 구간이다. reflow가 발생하지 않는 것을 찾아볼 수 있다. reflow 현상은 웹 성능에 직접적인 영향을 주기 때문에, reflow를 최대한 안 일어나게 하는 것만으로도 성능을 높힐 수 있을 것 같다.


그 다음으로 intersectionObserver를 React에서 어떻게 활용했는지 설명하려고 한다.


이 프로젝트의 랜딩페이지에는 수많은 스크롤 애니메이션이 들어가는데, 컴포넌트마다 매번 호출하는 것은 굉장히 번거롭다고 생각했다. 그래서 나는 React Hook으로 커스터마이징을 해서 useFadeIn 함수를 만들었다.


const useScrollFadeIn = ({ direction, duration, delay }) => {
  const element = useRef(null);

  // TODO: 요소 방향 설정
  const onDirection = (name) => {
    switch (name) {
      case "up":
        return "translate3d(0, 20%, 0)";
      default:
        return;
    }
  };

  // TODO: observer 콜백 함수
  const onScroll = useCallback(
    ([entry]) => {
      const { current } = element;
      if (entry.isIntersecting && current) {
        current.style.opacity = "1";
      }
    },
    [delay, duration]
  );

  useEffect(() => {
    let observer;

    if (element.current) {
      observer = new IntersectionObserver(onScroll, { threshold: 0.7 });
      // TODO: 옵져버 시작
      observer.observe(element.current);
    }

    // TODO: 옵져버 중지
    return () => observer && observer.disconnect();
  }, [onScroll]);

  return {
    ref: element,
    style: { opacity: 0, transform: onDirection(direction) },
  };
};

// landing.jsx
// 실제 사용 예제
const animate = {
  up: useScrollFadeIn({ direction: "late-up", duration: 1, delay: 0.8 }),
};

// 컴포넌트
<Arrow {...animate.up}>


React Hook의 장점중에 하나인 Hook 커스터마이징을 통해 Hook 함수를 계속 재활용해서 사용하였다.




observer


하지만 intersectionObserver를 사용해도 문제가 없는건 아니였다. 보통 프레임이 16.7ms를 넘지 않으며 초당 60fps 미만으로 프레임을 렌더링해야 부드러운 애니메이션이 나오는데, frames칸을 보면 빨간색 줄이 많이 그어져있다.. 자세히보니 16.7ms를 초과하여 16.7ms ~ 60fps 이상 정도 수준이 빨간색으로 표시가 되었다.


performance 탭을 통해 어떤 파일이 먼저 렌더링 되는지, 렌더링 순서를 파악하고 더 효율적으로 개선할 수 있다. 이를 통해 내가 깨달은 점은, 프로젝트를 하면서 성능 부분에 대해서는 깊히 고려해보지 못했다는 것이다. 프론트 개발에서 성능 개선에 대해 구글링을 하면 정말 많은 자료들이 나온다.


하지만 아직 performance 탭에 능숙하지 않은 나는 정확하게 어느 부분을 더 개선해야 하고, 성능을 극대화 시키기 위한 방법을 정확하게 찾지 못했다. 물론 reflow를 최대한으로 줄이고, 위에 설명했던 방법처럼 intersectionObserver를 활용한 방법도 좋을 것 같다.


하지만 나는 또 다른 방법으로 웹 최적화를 도와주는 사이트를 찾은 적이 있었는데, 바로 성능 개선을 위해 구글에서 지원하는 develpoer 사이트이다. 더 구체적으로 체크하고 개선하기 위해 이 사이트를 참고해 보았다.
구글 웹 페이지 속도 체크


google-developers


google-developers2



확실히 이 사이트를 활용해서 개선할 사항들을 명시적으로 보니 이해가 된다. 그리고 우려했던 arrow.png 이미지 파일… 이게 바로 addEventListener를 써서 구현했던 이미지 파일이다.


그 외에도 개선 사항들이 많은데 차근 차근 살펴보면서 개선해보면 재밌겠다는 생각이 들었다.


그래도 우려했던대로 확실히 랜딩페이지 부분에서 문제점이 많이 나왔고, 이를 해결하기 위해 많은 노력이 필요할 것으로 보인다. 자바스크립트로 현재 만들고 있는 프로젝트가 마무리가 된다면 리팩토링을 시작해볼 생각이다.


리팩토링은 최대한 성능 최적화에 신경 쓸 계획이며, 이미지 최적화에 많은 노력을 쏟을 계획이다.







마무리.

프로젝트이후에 많은 것을 배웠고, 이제는 성능 최적화까지 신경쓰며 개발하는 개발자가 되고 싶다. 이전에 공부를 하면서, 또 블로그를 쓰면서 느꼈던 점은 사용자 경험을 깊게 고민하는 과정이 정말 재밌게 느껴진다. 그리고 사용자 문제점을 빠르게 파악하고, 이를 바로 해결할 수 있는 개발자가 되고 싶다!




나중에 참고할 만한 자료 - Image Optimization
나중에 참고할 만한 자료 - CSS 성능 개선 방법
나중에 참고할 만한 자료 - 페이지 렌더링 체크 방법