[React] 메뉴 컴포넌트 키보드 탐색 스크롤 로직

RuLu·2025년 3월 27일
1

React

목록 보기
14/15
post-thumbnail

프론트에서 메뉴 컴포넌트를 만들 때 필수적으로 들어가야 하는 로직은 키보드를 이용한 메뉴 컴포넌트 탐색 로직이다.

메뉴 컴포넌트가 너무 길어서 안에 스크롤이 생기게 되었을 때 키보드 탐색 방향에 맞게 메뉴 아이템이 포커스 되어야한다.
예를 들어 분기처리를 해보자면,

  1. 위에서 아래 방향으로 키보드 탐색을 진행할 때는 아래 포커스 고정으로 스크롤이 내려가야 한다.
  2. 아래에서 위 방향으로 키보드 탐색을 진행한다면 위 포커스 고정으로 스크롤이 올라가야 한다.
  3. 현재 보이는 컨테이너 안에서만 탐색이 진행된다면 스크롤이 이동하면 안된다.
  4. 맨 마지막 아이템에서 아래 방향키를 누르면 첫 번째 아이템이 포커스 되어야하고
  5. 첫 번째 아이템에서 윗 방향키를 누르면 맨 마지막 아이템이 포커스 되어야한다.

그런데 내 원래 로직은 상단으로만 고정되었음,,, 그래서 어떤 모양이었나면

그룹에서는 스크롤이 작동안하고, 일반 메뉴리스트에서는 무조건 상단 고정이되고 있었음.. 사실 그룹에서 스크롤 작동 안하는건 그룹의 하위에 있는 child를 구하지 않아서 그런거였고 나머지는 여기 부분이 문제였음

//* 해당하는 자식에 맞게 scroll
  const scrollToChild = (child: Element) => {
    if (!(child instanceof HTMLLIElement)) {
      return
    }

    const { offsetTop } = child

    ref.current?.scrollTo(0, offsetTop)
  }

매개변수로 받는 child가 다음에 포커스 할 요소인데 그냥 그걸 맨 위로 가게 스크롤 짠거..
이 부분을 뜯어 고쳐보자!

먼저 메뉴 스크롤 코드에서 ref로 정보를 뽑아내는 주체는 크게 2개이다.
컨테이너(메뉴)에서 clientHeight, scrollTop, scrollHeight
메뉴 아이템에서 clientHeight, offsetTop

clientHeight: 화면상 보이는 요소의 높이

scrollHeight: 스크롤로 가려진 부분까지 합친 요소의 전체 높이

scrollTop: 화면상 보이는 시작점이 맨 위에서 얼마큼 떨어져 있는지

offsetTop: 각 요소가 부모요소의 시작점으로부터 얼마큼 떨어져 있는지

ref가 걸려있는 곳은 메뉴컨테이너고

child는 탐색 로직에서 구한 다음에 포커스 될 요소이다.

위 정보들을 가지고 위에서 구한 1~5까지의 분기처리를 진행해야한다.

수도 코드

먼저 화면을 벗어나는 것은 신경쓰지 않고 탐색 방향만을 구한다.

위로 탐색할 때

현재 child의 offsetTop + clientHeight가 ref의 scrollTop + clientHeight보다 작다.

아래로 탐색할 때

현재 child의 offsetTop + clientHeight가 ref의 scrollTop + clientHeight보다 크다.

위 두가지 경우에서 분기 쳐야하는 것은 3개이다.

  • 맨 마지막에서 맨 위로 (마지막 아이템에서 아래 방향키)
  • 맨 위에서 맨 마지막으로 (첫번째 아이템에서 위 방향키)
  • 메뉴 탐색이 화면을 벗어나지 않을 때

이걸 코드에 녹여보자.

코드

//* 이동방향에 따라 스크롤 위치 로직 (아래로가면 아래 고정, 위로가면 위로 고정)
  const scrollToChild = (child: Element) => {
    if (!(child instanceof HTMLLIElement)) {
      return
    }
    const { offsetTop, clientHeight } = child

    const menuHeight = ref.current?.clientHeight
    const scrollTop = ref.current?.scrollTop

		// 메뉴 ref가 제대로 잡히지 않았다는 것
    if (menuHeight === undefined || scrollTop === undefined) return 

		// 키보드 캄색이 화면에서 벗어나지 않았을 때 얼리리턴
    if (scrollTop < offsetTop && offsetTop + clientHeight < scrollTop + menuHeight) return

    if (menuHeight && scrollTop !== undefined && scrollTop + menuHeight < offsetTop + clientHeight) {
      //* 아래로 갈때 하단 픽스
      const scrollToPosition = offsetTop + clientHeight - menuHeight
      ref.current?.scrollTo(0, scrollToPosition + paddingHeight)
    } else if (menuHeight && scrollTop && scrollTop + menuHeight > offsetTop + clientHeight) {
      //* 위로갈 때 상단 픽스
      ref.current?.scrollTo(0, offsetTop)
    }
  }

결과

깰~꼼!

profile
프론트엔드 개발자 루루

0개의 댓글