본문 바로가기
JavaScript

JavaScript - 마우스 커서에 달린 툴팁 최적화하기 (mousemove)

by 새발개발JA 2024. 2. 1.
반응형

 

 

 

마우스 오버시 커서를 따라다니는 툴팁을 구현하며

리팩토링하는 과정을 간결하게 기록해보았다

 

이제는 어떻게 라는 질문이 머리속에 제일 먼저 맴돌기 시작한다

오늘 아침에 출근하면서 본 토스 컨퍼런스의 인트로 영상 문구가 생각났다

내가 좀 더 고생해야 사용하는 유저의 시간을 아낄 수 있다

최적화라는 단어에 대해 고심하게 되는 시기가 온 것이다

 

 

내가 좀 더 고생해야 사용하는 유저의 시간을 아낄 수 있다

 

 


 

JavaScript - 마우스 커서에 달린 툴팁 최적화하기 (mousemove)

 

 

첫번째 이슈  - useState

 

mousemove 이벤트를 통해 실시간으로 좌표값을 받아와 useState 에 저장하여 → 툴팁에 style 값으로 넣어주는 방식을 사용하였다. 그렇게 되니 이슈가 생겼다. mousemove 시에 계속 함수를 호출되어 그안의 setState 가 호출되어 렌더링을 일으킨다.

state 가 바뀌면 화면 렌더링을 일으키는데 좌표같은 세세한 값이 매순간마다 저장되니 성능에 좋지 않았다

 // 커서 좌표 state
 const [cursorPosition, setCursorPosition] = useState<CursorPosition>({
    x: 0,
    y: 0,
  });

  // 실시간 좌표 값을 엘레멘트에 반영
  const mouseCursor = useMemo(() => {
    return (
      <div
        className="tooltip"
        style={{
          left: `${cursorPosition.x}px`,
          top: `${cursorPosition.y - 30}px`,
        }}
      >
        {`${now.hours} : ${now.minutes} : ${now.seconds}`}
      </div>
    );
  }, [cursorPosition, now.hours, now.minutes, now.seconds]);

  // 마우스 움직일 때 실시간 좌표값을 저장해주는 함수
  const handleMouseMove: MouseEventHandler<HTMLDivElement> = (e) => {
    const scrollTop = clockRef.current?.scrollTop || 0;

    setCursorPosition({
      x: e.clientX,
      y: e.clientY + scrollTop,
    });
  };

 

 

그래서 state 를 사용하지 않고 직접 돔을 변경하였다.

직접 돔을 변경하게되면 프로젝트가 커지게 되면 나중에 돔컨트롤이 어려울 수 있다는 단점이 있지만,

마우스 무브시 매순간 state 가 렌더링이 되는 것보다는 비용이 적다고 생각하였기 때문이다.

그렇게 되니 코드가 한결 간결해졌다. useState 와 useMemo 를 사용하지 않게 되었다.

  const handleMouseMove: MouseEventHandler<HTMLDivElement> = (e) => {
    const scrollTop = clockRef.current?.scrollTop || 0;
    const tooltip = document.querySelector(".tooltip") as HTMLElement;
    
    // 직접 style 에 좌표값을 추가
    tooltip.style.left = `${e.clientX}px`;
    tooltip.style.top = `${e.clientY + scrollTop - 30}px`;
  };

 

 

 

 

 

두번째 이슈  - Throttling

 

마우스 무브 이벤트가 일어날 때마다 state를 제거하여 불필요한 렌더링은 사라졌지만 매순간 함수는 호출된다.

역시 불필요한 호출이다. 이 툴팁의 움직임도 애니메이션이기 때문에 1초당 60fps 가 보여지는 것이 적당하다고 생각했다.

 

그래서 throttling 을 사용해보기로 했다. throttling 은 연속으로 호출되는 이벤트 함수의 경우 매우 유용하게 사용될 수 있다. 

그래서 현재의 시간과 지정 타이머의 시간을 비교하여 연속으로 실행하는 useThrottle 훅을 통해

마우스무브함수가 16.6밀리초마다 실행되도록 하여 60fps 에 맞추어 필요한 만큼만 호출해주었다

 

 

 

BUT

useThrottle 도 함수이기 때문에 mouseMove 이벤트가 호출될 때 이 함수 또한 계속 호출된다. 결국 return 값으로 타이머에 따라 실행되도록 했지만 호출은 똑같이 된다. 또한 내부에서 setState 를 하기때문에 state 또한 바뀌어 렌더링이 된다.

throttling 기법을 사용할 떄는 비용을 생각해야 한다. (무거운 쿼리를 사용하는  리스트 api 호출시 사용하는 편이 더 나을 것 같다)

1. 마우스 이벤트 핸들러에 의해 usethrottle 훅이 매번 호출
2. 매번 호출되면서 setState 를 하게 됨 - 일정 시간 이후마다 setState 를 함
3. 즉 첫번째 useMemo 를 쓰는 방법보다는 낫지만 그냥 마우스함수 내에서 직접 접근을 통해 돔 style 핸들링 하는 편이 더 나을 수 있다

useThrottle 훅 >> setState >> mouseMove함수 >> DOM 스타일링
mouseMove함수 >> DOM 스타일링

 

 

 

 

 

반응형

댓글