본문 바로가기

프로젝트/세컨핸드

[Trouble Shooting] 사용자 디바이스에 따른 다수의 이미지 조회 방법

문제정의

- 프로젝트의 기획에는 상품의 조회(상세페이지)에서 상품의 이미지가 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>
...

 

배운 점

- 처음에는 기획서에 있는 내용으로만 프로젝트를 진행했지만, 주어진 것만 개발하는 것이 아닌 개발 단계에서 필요한 기능들에 대해서 고민하고 이를 추가로 만드는 것도 개발자의 역할이라는 생각을 했다.