@egjs/react-conveyer

배경

출장항공권 FE 작업을 할때
PC 환경에서 다음과 같이 필터 카테고리 탭을 스크롤하는 UI 기능 구현이 필요했습니다

  • 탭이 스크롤 되게 함
  • 양옆으로 움직이게 하는 좌우버튼을 추가하고
  • 좌우 끝 구간에 도달하면, 버튼을 미노출

이걸 스크립트 작업으로 구현하려고 생각해보니 수작업을 꽤 많이 해야할것같아서
좀더 편하게 작업할수있는 방법이 없을까 해서 라이브러리 를 찾아보았고
@egjs/react-conveyer 가 요구사항에 적합하다고 판단되어
이 라이브러리를 사용해서 아래와 같이 좌우 스크롤/버튼 구현을 완성했습니다

이와 유사한 좌우 스크롤/버튼을 구현하실 일이 있을것 같아서
출장항공권 코드를 예시로 적용법을 간단히 공유드리려고 합니다

소개

@egjs/react-conveyer 는 native scroll UI 에 drag 제스쳐
스크롤 이벤트 관련 변수/함수를 제공해주는 라이브러리 입니다

라이브러리 코드 한줄로

const { isReachStart, isReachEnd, scrollIntoView } = useConveyer(ref);

수작업으로 하면 꽤 힘들었을 다음 스펙들을 해결할수 있었습니다

  • 수평스크롤 움직임에 따라 좌우버튼을 선택적으로 노출
  • 좌우 버튼으로 스크롤 이동

출장항공권에 적용한 코드를 예시로 어떻게 적용했는지 구체적으로 설명드리도록 하겠습니다

라이브러리에 대한 상세 사용법은 @egjs/react-conveyer document 참조해주시면 되겠습니다

코드

우선 적용한 전체 코드는 다음과 같습니다

import { useConveyer } from '@egjs/react-conveyer';

const Tab = ({ selectedTag, setSelectedTag }: IProps) => {
const ref = useRef<HTMLDivElement>(null);
const { isReachStart, isReachEnd, scrollIntoView } = useConveyer(ref);

return (
<div className={cx('tabs_scroll_container')}>
<div className={cx('tabs_scroll_box')} ref={ref}>
{filterTagList.map((tagItem, idx) => (
<button
key={`${tagItem}_${idx}`}
className={cx('tab')}
type="button"
aria-selected={tagItem === selectedTag}
onClick={() => setSelectedTag(tagItem)}
>
<span className={cx('name')}>{tagItem}</span>
</button>
))}
</div>
{!isReachStart && (
<button
type="button"
className={cx('prev')}
aria-label="이전"
onClick={() => {
scrollIntoView('start', {
align: 'end',
duration: 500,
});
}}
/>
)}
{!isReachEnd && (
<button
type="button"
className={cx('next')}
aria-label="다음"
onClick={() => {
scrollIntoView('end', {
align: 'start',
duration: 500,
});
}}
/>
)}
</div>
);
};

export default Tab;

적용부분별로 나눠서 설명드리겠습니다

target element 지정

우선 @egjs/react-conveyer 를 적용할 대상을 지정해줘야 합니다

const ref = useRef<HTMLDivElement>(null);
useConveyer(ref);

`useConveyer(ref)` hook에 파라미터로 넘겼던 ref 참조를
scroll item 들을 포함하고 있는 부모 element ref 로 지정해줍니다

<div className={cx('tabs_scroll_box')} ref={ref}>
{filterTagList.map((tagItem, idx) => (
<button >
// ...
</button>
))}
</div>

useConveyer

이제 본격적으로 라이브러리를 사용하면 되는데요

const { isReachStart, isReachEnd, scrollIntoView } = useConveyer(ref);

`useConveyer` hook 으로 스펙 구현에 필요한 변수/함수를 가져옵니다

  • isReachStart : 스크롤이 시작부분에 (왼쪽) 도달했는지 확인해주는 boolean 값
  • isReachEnd : 스크롤이 끝부분에 (오른쪽) 도달했는지 확인해주는 boolean 값
  • scrollIntoView : 영역 밖에 있는 item 들을 스크롤해서 display zone 으로 끌어온다고 생각하시면 되는데..
    우선 다음 영상으로 개략적으로 참고해주시기 바랍니다

세부설명은 document 를 참조

⬅️ 버튼

{!isReachStart && (
<button
type="button"
className={cx('prev')}
aria-label="이전"
onClick={() => {
scrollIntoView('start', {
align: 'end',
duration: 500,
});
}}
/>
)}
  • `!isReachStart` : 스크롤이 시작부분에 오지 않았을때만 노출하도록 합니다
  • `scrollIntoView(‘start’, {align: ‘end’})` : display zone 의 start 영역으로 바깥에 있는 element 들을 가져옵니다.
    그리고 display zone 의 end 영역에 item 이 위치하도록 각을 맞춥니다

➡️ 버튼

{!isReachEnd && (
<button
type="button"
className={cx('next')}
aria-label="다음"
onClick={() => {
scrollIntoView('end', {
align: 'start',
duration: 500,
});
}}
/>
)}
</div>
  • `!isReachEnd` : 스크롤이 끝부분에 오지 않았을때만 노출하도록 합니다
  • `scrollIntoView(‘end’, {align: ‘start’})` : display zone 의 end 영역으로 바깥에 있는 element 들을 가져옵니다.
    그리고 display zone 의 start 영역에 item 이 위치하도록 각을 맞춥니다

마치며

아무래도 view library 설명이다 보니 글만으로는 이해가 어려우신 부분도 있으실것 같은데요
공식문서에 설명이 잘 되어있으니 이해 안되시는 부분은 문서 참고 부탁드립니다
특히 scrollIntoView 관련해서 잘 이해 안되시는 부분 있으시면 sample 한번 보시면 도움이 되실것 같습니다

--

--