React Swipeable 이미지 슬라이더 커스텀

김동균·2022년 1월 29일
3
post-thumbnail

react에서 swipeable 이미지 슬라이드를 만들어 보았다
기존에 있는 swipeable 이미지 슬라이드와 내가 만든 슬라이드와는 차이점이 두 개가 있다.
슬라이드를 react-easy-swipe 라이브러리를 사용해서 만들 예정이다. 라이브러리에서 swipe 기능만 가져오기 때문에 css를 수정하기에는 편할 것이다. 이것이 첫번째이고,
지금 만드는 이미지 슬라이드는 PC 화면에서는 swipe 동작이 되지 않고, 스마트폰 화면에서만 동작 된다. 이것이 두번째다. PC화면에서 확인하려면 F12 누르고, ctrl + shift + M 키를 눌러서 기기 툴바 전환해서 보면 된다.

사용할 이미지다. 이미지 width, height는 모두 동일하다.

const postData = {
  boardImageUrl: ["./img/cat.jpg", "./img/cat2.jpg", "./img/cat3.jpg"]
};

css는 emotion으로 작성했다. style-components나 css-in-js를 사용해도 상관없다.

const PostImage = styled.div`
  width: 300px;
  height: 300px;
  position: relative;
  overflow: hidden;
`;
const ImgDiv = styled.div`
  display: flex;
  width: 100%;
  height: 100%;
`;
const Img = styled.img`
  width: 100%;
  height: 100%;
  object-fit: cover;
`;

기본 틀이다. 3개의 사진이 일렬로 정렬되어 있고, overflow: hidden으로 첫번째 사진만 보이는 상태다.

<PostImage>
  <Swipe>
    <ImgDiv>
      {postData.boardImageUrl.map((imageUrl, index) => {
        return <Img key={index} src={imageUrl} alt="" />;
      })}
    </ImgDiv>
  </Swipe>
</PostImage>

변수를 두 개 만들어주자. 하나는 swipe할 때 사용할 x축 값이고, 하나는 몇 번째 이미지인지 알려줄 값이다.
초기값은 각각 0과 1이다.

const [positionx, setPositionx] = useState<number>(0);
const [imgCount, setImgCount] = useState<number>(1);

react-easy-swipe의 Swipe 컴포넌트에는 여러 개의 함수가 있는데 그 중에 onSwipeMove와 onSwipeEnd를 사용할 예정이다.
onSwipeMove는 화면의 드래그를 시작한 지점부터 드래그하여 위치한 지점까지의 픽셀값을 움직임에 따라 실시간으로 position.x, position.y로 알려준다. 여기서 우리는 position.x만 사용할 예정이다.

export default function App() {
  ...
  
  const onSwipeMove = (position: { x: number }) => {
    setPositionx(() => position.x);
  };
  const onSwipeEnd = () => {
    
  };

  return (
  <PostImage>
    <Swipe onSwipeMove={onSwipeMove} onSwipeEnd={onSwipeEnd}>
      <ImgDiv positionx={positionx}>
        {postData.boardImageUrl.map((imageUrl, index) => {
          return <Img key={index} src={imageUrl} alt="" />;
        })}
      </ImgDiv>
    </Swipe>
  </PostImage>
  );
}

const ImgDiv = styled.div<ImgDivProps>`
  display: flex;
  width: 100%;
  height: 100%;
  transform: translateX(${({ positionx }) => `calc(${positionx}px)`});
`;

다음과 같이 하면 swipe에 따라 이미지가 움직이는 것을 볼 수 있다. 하지만 움직임이 끝나면 그 상태로 이미지가 멈추고, 이미지를 이동하면 다시 처음으로 돌아간다.😥

onSwipeEnd 함수는 swipe가 끝나고 손을 뗐을 때 호출된다. 20px이 이상 넘어간 후 손을 떼면 다음 이미지로 넘어가도록 onSwipeEnd 함수에 추가해주자.
그리고 그에 맞게 css도 수정해주자.

const onSwipeEnd = (position: { x: number }) => {
  if (positionx < -20) {
    setImgCount((imgCount) => imgCount + 1);
  }
  if (positionx > 20) {
    setImgCount((imgCount) => imgCount - 1);
  }
  setPositionx(() => 0);
};

const ImgDiv = styled.div<ImgDivProps>`
  display: flex;
  width: 100%;
  height: 100%;
  transform: translateX(${({ positionx }) => `calc(${positionx}px + ${-100 * (imgCount - 1)}%)`});
`;


swipe가 되면 이미지가 잘 바뀌는 것을 볼 수 있다. 하지만 부족한 부분이 있다. 다음 이미지가 없어도 swipe하면 넘어간다.
onSwipeMove에 예외사항들을 추가해주자.

const onSwipeMove = (position: { x: number }) => {
  if (postData.boardImageUrl.length === 1) {
    return;
  }
  if (imgCount === 1 && position.x < 0) {
    setPositionx(() => position.x);
    return;
  }
  if (imgCount > 1 && imgCount < postData.boardImageUrl.length) {
    setPositionx(() => position.x);
    return;
  }
  if (imgCount === postData.boardImageUrl.length && position.x > 0) {
    setPositionx(() => position.x);
    return;
  }
};

이미지가 확확 바뀌는 것도 맘에 들지 않는다. 변수 하나를 추가하여 swipe하는 도중에는 transition을 0s로 하고, swipe가 끝나면 transition을 0.2s로 설정한다.

const onSwipeEnd = (position: { x: number }) => {
  if (positionx < -20) {
    setImgCount((imgCount) => imgCount + 1);
  }
  if (positionx > 20) {
    setImgCount((imgCount) => imgCount - 1);
  }
  setPositionx(() => 0);
  setEndSwipe(true);
};

const onSwipeMove = (position: { x: number }) => {
  setEndSwipe(false);
  if (postData.boardImageUrl.length === 1) {
    return;
  }
  if (imgCount === 1 && position.x < 0) {
    setPositionx(() => position.x);
    return;
  }
  if (imgCount > 1 && imgCount < postData.boardImageUrl.length) {
    setPositionx(() => position.x);
    return;
  }
  if (imgCount === postData.boardImageUrl.length && position.x > 0) {
    setPositionx(() => position.x);
    return;
  }
};

const ImgDiv = styled.div<ImgDivProps>`
  display: flex;
  width: 100%;
  height: 100%;
  transition: transform ${({ endSwipe }) => (endSwipe ? "0.2s" : "0s")};
  transform: translateX(
    ${({ imgCount, positionx }) =>
      `calc(${positionx}px + ${-100 * (imgCount - 1)}%)`}
  );
`;

이미지 개수를 파악하기가 힘들다.
instagram 게시물처럼 몇 개의 이미지 중에 몇 번째 이미지인지 표시해주는 ImageCounter를 만들어주자.

{postData.boardImageUrl.length > 1 && (
  <ImageCounterWrapper>
    {postData.boardImageUrl.map((props, index) => {
      return (
        <ImageCounter key={index} index={index} imgCount={imgCount} />
      );
    })}
  </ImageCounterWrapper>
)}

export const ImageCounter = styled.div<ImageCounterProps>`
  width: 6px;
  height: 6px;
  background: ${(props) =>
    props.index === props.imgCount - 1 ? "#0095f6" : "#a8a8a8"};
  border-radius: 50%;
  &:not(:last-of-type) {
    margin-right: 4px;
  }
`;

export const ImageCounterWrapper = styled.div`
  display: flex;
  justify-content: center;
  align-items: center;
  margin-bottom: 15px;
  margin-top: 15px;
`;

완성! 깔끔하게 잘 되는 것을 볼 수 있다.
추가로 PC화면을 위해서 버튼 만들어주는 것이 좋을 것 같다. 버튼은 누르면 imgCount 값을 변경하는 것으로 쉽게 만들 수 있다.

완성된 코드는 https://codesandbox.io/s/react-typescript-swipeable-image-slider-nzvri 여기서 확인할 수 있습니다.

profile
초보 개발자

0개의 댓글