Taskify - 가로 스크롤 기능 구현

박상준·2024년 6월 13일

좌우 스크롤 기능

오늘도 돌아온 추가기능 구현이다. 우리의 서비스중 할일을 보여주는 대시보드 페이지는 세로 스크롤이 아니라 가로 스크롤로 구현되어 있다. 원래라면 스크롤바를 움직여 이동할 수 있지만 여러 곳에서 스크롤바가 생기는 바람에 우선 스크롤바를 숨겨놓기로 했다. 이런 방법을 사용하면서 좌우 스크롤 기능이 있는 마우스와 트랙패드를 제외하고는 스크롤을 움직이기 어렵게 되었다.

그래서 마우스로 클릭하고 움직이면 좌우로 스크롤 되도록 기능 구현을 하기로 했다.

좌우 스크롤 기능 구현하기

이 기능은 요소에 접근해 위치를 바꿔주는 역할이기 때문에 드래그앤드롭과 같이 ref를 통해 DOM에 접근해야 한다.

  const [isDrag, setIsDrag] = useState(false)
  const [totalX, setTotalX] = useState(0)
  const scrollRef = useRef<HTMLDivElement>(null)

3개의 값을 먼저 만들어 주겠다. drag상태를 보여주는 drag state값과 마우스가 이동한 값을 저장하는 totalX, 스크롤할 요소에 지정하는 scrollRef값이다. 이제 3가지 상황에 맞는 로직을 구현해보겠다.

드래그 시작

  const onAxisDragStart = (e: MouseEvent) => {
    setIsDrag(true)
    const xAxis = e.clientX
    if (scrollRef.current && 'scrollLeft' in scrollRef.current) {
      setTotalX(xAxis + scrollRef.current.scrollLeft)
    }
  }

드래그 시작할때는 드래그 상태를 나타내는 state값을 true로 만들어준다. 그리고 현재 마우스를 클릭한 위치중 x값을 저장해주고 만약 이미 스크롤이 이동해있을 경우를 대비해 현재 마우스의 위치와 이동한 만큼의 위치를 합쳐 해당 값을 저장해준다.

드래그 중

  const onAxisDragMove = (e: MouseEvent) => {
    if (!isDrag) {
      return
    }
    const scrollLeft = totalX - e.clientX
    if (scrollRef.current && 'scrollLeft' in scrollRef.current) {
      scrollRef.current.scrollLeft = scrollLeft
    }
  }

이제 드래그 중에 동작할 로직이다. 아까 저장한 위치의 값을 이용해 스크롤을 움직여주는 것이다. 저장한 위치의 값에서 현재 움직인 마우스의 값을 뺀 값만큼 스크롤을 움직여주는 것이다. 드래그중이 아닐때에는 early return을 통해 함수 동작을 방지한다.

드래그 종료

  const onAxisDragEnd = () => {
    if (!isDrag) return
    if (!scrollRef.current) return
    setIsDrag(false)
  }

종료하는 시점에서도 앞의 동작중일때에는 동작하지 않도록 early return을 한다. 그리고 드래그상태를 false로 변경해주고 마무리한다.

문제점.1

약간의 문제점이 있다. 현재 이 페이지의 카드 개별마다 드래그앤 드롭 이벤트가 걸려있고 이번에 전체를 감싸는 div태그에 좌우 스크롤 이벤트를 적용했다. 그래서 카드를 드래그하는 과정에서 div의 이벤트도 동작한다는 점이다. 그래서 이벤트 위임을 막는 함수를 넣어봤다.

  const preventEffects = useCallback((e: MouseEvent) => {
    e.preventDefault()
    e.stopPropagation()
  }, [])

useCallbakc을 이용해 버블링과 캡쳐링을 막는 함수를 만들고

  const onAxisDragStart = (e: MouseEvent) => {
    preventUnexpectedEffects(e)
...

스크롤 함수에 넣어줘서 이벤트 위임을 막았다. 그랬더니 카드 자체에 드래그앤드롭 이벤트가 동작하지 않았다. 이 문제는 preventDefault때문에 생기는 것이었다. 브라우저에서는 드래그의 기본 이벤트로 요소를 끌어다 쓰거나 택스트를 드래그하는 이벤트가 등록되어 있다. 그리고 할일 카드에서도 해당 기능을 이용해서 기능 구현을 한것이였다. 하지만 할일 카드를 감싸는 요소의 드래그 이벤트를 없애면서 드래그앤드롭 기능도 안되는 것이었다.

해결방안.1

해결방안으로 우선 부여한 preventDefault를 제거해줬다.

  const preventEffects = useCallback((e: MouseEvent) => {
    e.preventDefault()
  }, [])

문제점.2

이제 두개의 이벤트는 모두 동작하지만 아직도 두개의 이벤트가 동시에 동작하는 문제가 생겼다. 카드를 드래그앤드롭을 이용해 옮긴 후에 좌우 스크롤의 종료 이벤트가 실행되지 않았기 때문에 발생하는 문제였다.

해결방안.2

해결방안으로 좌우 스크롤 이벤트의 시작 여부를 저장하는 isDrag값을 직접 세팅하도록 설정했다.

  const dragStart = (card: Card, id: number) => {
    setIsDrag(false)
    dragItem.current = card
    baseColumn.current = id
  }

전에 만들어둔 드래그앤드롭 이벤트에서 사용한 드래그 시작 로직이다. 내가 여기에서 setter함수를 통해 컨트롤한 이유는 console로 확인했을때 좌우 스크롤의 이벤트가 먼저 실행된 이후에 카드에 등록된 드래그앤드롭이벤트가 실행되었기 때문이다. 그래서 드래그앤드롭 이벤트가 실행되면 좌우 스크롤 이벤트를 종료해 스크롤이 이동되지 않도록 한것이다.

개선점

앞으로 개선해야할 점은 아무래도 마우스가 움직이는 것을 계속 트래킹하면서 움직이다보니 브라우저에 불필요한 요청을 할것이다. 그래서 저번에 검색기능에서 사용한 deBounce기능이나 throtle기능을 이용해 의도적으로 딜레이시킨 값을 사용해 동작하는 것이다. 이 기능은 조만간 리팩토링해보겠다.

마무리

해당 기능을 구현하게 되어서 기쁘다. 왜냐하면 이 기능을 구현함으로서 모바일에서도 터치로 대시보드를 움직일 수 있기 때문이다. 물론 지금 코드가 완벽한가는 의문이지만 처음부터 잘할수는 없는 일이니까~

이제 진짜 며칠 안남았다. 남은 기간동안 구현한 기능들을 다시 돌아보고 추가적으로 사용자가 불편을 겪을만한 부분을 수정해보는 시간을 가져보면 좋을 것 같다. 물론 해당 서비스가 진짜 고객을 대상으로 하는 서비스는 아니지만 이럴때 미리 감을 익히고 어떤 부분을 조심하면 좋을지 생각해보면 좋지 않을까 싶다. 남은 기간 최선을 다해보자.

profile
개인 블로그 플렛폼도 운영중입니다(https://blog-park.vercel.app/)

0개의 댓글