문제정의
- 프로젝트의 기획에는 상품의 조회(상세페이지)에서 상품의 이미지가 1개 이상인 경우, 모바일 기기의 슬라이드 기능으로 다수의 이미지를 조회할 수 있게 되어있었다.
- 하지만, 모바일 기기가 아닌 웹에서 접근시에는 다수의 이미지를 조회할 수 있는 기능이 누락되어 있었고, 이에 사용자의 디바이스에 따라 다수의 이미지 조회 방법을 다르게 구현하는 작업이 필요했다.
- 해당 작업을 위해서는 먼저 사용자의 디바이스를 조회해야했고, 기존 슬라이드 기능 외에 추가로 캐러셀 기능 구현이 필요했다.
시도한 방법 및 해결방안
1. 사용자의 디바이스 조회 ⭕️
- 사용자의 디바이스 조회는 window.navigator의 userAgent 속성을 사용했다.
- userAgent는 HTTP 요청을 보내는 디바이스와 브라우저 등 사용자 소프트웨어의 식별 정보를 담고 있는 request header의 한 종류이다.
* userAgent의 실제 예시
Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.93 Safari/537.36
// 사용자의 디바이스가 모바일 기기인지 확인하는 정규 표현식(boolean 값)
// 사용자의 디바이스가 중간에 변경되는 일은 없다고 판단하여 일반 변수로 선언
const isMobileDevice =
/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
window.navigator.userAgent
);
- userAgent와 정규 표현식을 통해 현재 사용자의 기기가 모바일 기기인지 판단하였다. 만약 모바일 기기라면 isMobileDeivce 변수는 true, 모바일 기기가 아니라면 false가 된다.
2. 캐러셀 구현 ⭕️
- 이전 바닐라 JavaScript를 통해 캐러셀을 구현해본적이 있었지만, 리액트에서는 처음으로 캐러셀을 구현했다.
- 캐러셀을 구현할 때, 고려했던 부분은 다음과 같다.
- 첫 번째, 사용자가 보게 되는 이미지와 이를 제외한 나머지 부분을 보이지 않게 가려주는 window(Carousel)
- 두 번째, 전체 이미지를 담고 있는 container(CarouselContainer)와 선택된 인덱스에 따라 사용자가 보게 되는 이미지를 변경시켜주는 것
- 세 번째, previous와 next 버튼으로 사용자가 보게 되는 이미지의 인덱스를 변경하는 것
- 첫 번째와 두 번째 고려한 부분을 코드로 구현하면 다음과 같다.(프로젝트에서는 styled-components를 사용했다.)
- 먼저 사용자가 보게 되는 이미지와 이를 제외한 나머지 부분을 보이지 않게 가려주는 Carousel의 스타일 적용이다.
- width를 하나의 이미지의 width로 지정하고, overflow-x : hidden 속성으로 이외 이미지는 보이지 않게 했다.
- 다음으로는 전체 이미지를 담고 있는 CarouselContainer의 스타일 적용이다.
- width는 전체 이미지 개수 * 하나의 이미지의 width로 하고, transform과 translateX를 활용하여 사용자가 선택한 인덱스에 따라 CarouselContainer를 이동하여 사용자에게 인덱스에 맞는 이미지를 보여줬다.
// 사용자가 보게 되는 이미지를 표시하고, 해당 이미지를 제외한 부분은 가려준다.
export const Carousel = styled.div<CarouselStyleProps>`
width: ${({ width }) => width}px;
height: ${({ height }) => height}px;
overflow-x: hidden;
position: relative;
`;
// 전체 이미지를 담고 있는 container
// transform과 translate를 활용하여 선택된 인덱스에 따라 사용자에게 보여지는 이미지를 변경
export const CarouselContainer = styled.div<CarouselContainerStyleProps>`
display: flex;
width: ${({ width, imagesCounts }) => imagesCounts * width}px;
transform: translateX(
${({ currentCarousel, width }) => currentCarousel * width * -1}px
);
height: ${({ height }) => height}px;
`;
- 다음으로 previous와 next 버튼으로 사용자가 보게되는 이미지의 인덱스를 변경하는 기능을 구현했다.
- 이미지의 인덱스를 react의 state로 정의하고, 버튼 방향에 따라, setState로 인덱스를 변경했다.
// Carousel 컴포넌트
export const Carousel = ({
urls,
height = SLIDER_HEIGHT,
width = 393,
}: CarouselProps) => {
const [currentCarousel, setCurrentCarousel] = useState(0);
const moveCarousel = (direction: 'next' | 'previous') => {
const totalCarousel = urls.length;
setCurrentCarousel((prevSlide) => {
if (direction === 'previous') {
return prevSlide === 0 ? totalCarousel - 1 : prevSlide - 1;
}
return prevSlide === totalCarousel - 1 ? 0 : prevSlide + 1;
});
};
return (
<S.Carousel height={height} width={width}>
<S.CarouselContainer
height={height}
width={width}
currentCarousel={currentCarousel}
imagesCounts={urls.length}
>
{urls.map((url) => {
const fileName = getFileNameFromUrl(url);
return (
<S.CarouselImage
src={url}
key={fileName}
alt={fileName}
height={height}
width={width}
/>
);
})}
</S.CarouselContainer>
<CarouselPageIndicator
imageCounts={urls.length}
currentIndex={currentCarousel}
/>
{urls.length >= 2 && (
<S.CarouselButton
direction="previous"
onClick={() => moveCarousel('previous')}
>
<Icon size={48} name="chevronLeft" color="systemBackgroundWeak" />
</S.CarouselButton>
)}
{urls.length >= 2 && (
<S.CarouselButton direction="next" onClick={() => moveCarousel('next')}>
<Icon size={48} name="chevronRight" color="systemBackgroundWeak" />
</S.CarouselButton>
)}
</S.Carousel>
);
};
- 마지막으로 상품 조회(상세페이지)에서 isMobileDevice 값에 따라 이미지 슬라이드 혹은 캐러셀 조건부 렌더링을 했다.
// 상품 조회(상세페이지)에서 사용자의 디바이스에 따라 슬라이드 혹은 캐러셀 조건부 렌더링
...
<S.ImageContainer>
{isMobileDevice && <Slide urls={details?.images ?? []} />}
{!isMobileDevice && <Carousel urls={details?.images ?? []} />}
</S.ImageContainer>
...
배운 점
- 처음에는 기획서에 있는 내용으로만 프로젝트를 진행했지만, 주어진 것만 개발하는 것이 아닌 개발 단계에서 필요한 기능들에 대해서 고민하고 이를 추가로 만드는 것도 개발자의 역할이라는 생각을 했다.
'프로젝트 > 세컨핸드' 카테고리의 다른 글
[Trouble Shooting] UUID를 사용한 고유한 키 값 사용 (0) | 2024.01.23 |
---|---|
[기능 구현] 동적 portal 만들기 (0) | 2024.01.22 |
[기능 구현] 커스텀 훅(Custom Hook)을 사용하여 비동기 로딩 및 에러 상태 처리 (0) | 2024.01.22 |
[기능 구현] OAuth를 통한 로그인 기능 구현 (0) | 2024.01.22 |
[Trouble Shooting] 상품 수정 등록 후 뒤로가기 경로 문제 (0) | 2023.11.23 |