현재 react-device-detect를 통해 브라우저와 모바일의 UI/UX를 조금씩 다르게 구현하고 있다. 아래 이미지를 통해 브라우저와 모바일의 UI를 비교해볼 수 있다.
상단부터 브라우저, 모바일
- 모바일의 경우, 캠핑장 마커를 터치했을 때 상세 정보 안내로 인해 화면 전체가 가려지기 때문에 캠핑장 이름만 간단하게 보려는 유저에게는 불편함을 초래할 수 있다.
- 그렇기 때문에, 네이버 지도앱을 레퍼런스 삼아서 마커를 터치했을 때는 간단한 정보만 보여주고, swipe up했을 때 상세 정보로 확장되는 컴포넌트로 변환되게 하는 작업을 해보려고 한다.
swipe 예시
Drawer
에는 ResSpotInfo
라는 컴포넌트를 띄우게 되며, ResSpotInfo
는 단순히 모바일인지, 브라우저인지 구분해서 다른 컴포넌트를 띄우는 분기처리만 담당하는 컴포넌트이다. Drawer
컴포넌트에서 조건문으로 분기 처리해도 되지만, 굳이 depth를 한 단계 더 둔 이유는 Drawer가 담당하는 역할이 많아지게 되어 코드가 길고 복잡해지기 때문이다. const ResSpotInfo = () => {
const mDrawer = useRecoilValue(mDrawerState);
const SpotInfoUI = () => {
if (mDrawer === "expand" || isBrowser) {
return (
<Suspense fallback={<DrawerSkeleton />}>
<SpotInfo />
</Suspense>
);
} else {
return (
<Suspense fallback={<MDrawerSkeleton />}>
<MSpotInfo />
</Suspense>
);
}
};
return <SpotInfoUI />;
};
우선 touch event(start,move,end)에 touchHandler라는 switch문으로 event를 구분하고 그에 맞는 함수를 실행하는 함수를 등록했다.
touchstart는 touchmove와 값을 비교하여 움직인 거리를 계산하기 위해 달았으며, touchend는 움직인 값을 비교해서 일정 값을 만족하지 못하는 경우, 초기화하기 위해 달았다.
export const touchHandler = (evt, state, setState, scroll) => {
const { clientY } = evt.changedTouches[0]; // 마지막으로 이벤트가 발생한 위치
const { startY, swipeUp, swipeDown } = state;
const swipe = startY - clientY; // swipe한 거리
switch (evt.type) {
case "touchstart":
// startY, endY 초기화(startY=터치 이벤트가 처음 발생한 좌표)
setState({ ...state, startY: clientY, endY: null });
break;
case "touchmove":
// swipe Up,Down 구분
// Up할 경우 컴포넌트 확장 / Down할 경우 컴포넌트 축소
if (swipe > 0) {
setState({ ...state, swipeUp: swipe });
} else if (scroll && swipe < 0) {
setState({ ...state, swipeDown: swipe });
}
break;
case "touchend":
if (swipeUp && swipeUp >= 250) {
setState({
...state,
endY: clientY,
swipeDown: null,
swipeUp: null,
moveY: swipe,
});
} else if (swipeUp && swipeUp < 250) {
// 일정한 값을 못 넘길 경우, 초기화
setState({ ...state, swipeUp: null });
}
if (swipeDown && swipeDown >= -50) {
// 일정한 값을 못 넘길 경우, 초기화
setState({ ...state, endY: clientY, swipeDown: null, swipeUp: null });
} else if (swipeDown && swipeDown < -50) {
setState({ ...state, endY: clientY, swipeUp: null, moveY: swipe, swipeDown: null });
}
break;
default:
return;
}
};
MSpotInfo
컴포넌트에 css 속성으로 등록되어 있는 mDrawerContent 함수에 전역 상태의 swipeUp값을 인수로 전달하고 swipe값에 따라 컴포넌트가 위로 확장될 수 있게 했다.export const mDrawerContent = (swipe) => {
return css`
display: flex;
flex-direction: column;
justify-content: start;
align-items: flex-start;
margin-bottom: ${3 + swipe / 10}vh;
}
ResSpotInfo
컴포넌트에서 이 selector에 의해 컴포넌트가 변경된다. export default selector({
key: "mDrawerState",
get: ({ get }) => {
const mDrawer = get(mDrawerQuery);
const { moveY } = mDrawer;
if (moveY < -50) {
return "collapse";
}
if (moveY >= 250) {
return "expand";
}
return;
},
});
Drawer
컴포넌트에 useRef, onScroll eventHandler를 등록하여, scrollTop이 0경우, true의 값을 가지도록 boolean 형태의 전역 상태를 관리하도록 했다.