[React] 카드 슬라이더 만들기(1)

Reyna·2022년 12월 3일
1

Recursive

목록 보기
7/11

저번 주와 마찬가지로 javascript로 만들어진 카드 슬라이더를 리액트로 클론해보았다. 오늘은 navigation 기능까지 구현했다.

참고
https://wsss.tistory.com/1582

Swiper API
https://swiperjs.com/react

1. Swiper 라이브러리 설치

우선 Swiper라는 라이브러리를 사용할 것이므로 설치해주어야 한다.

npm i swiper

2. import

Swiper.jsx라는 컴포넌트를 만들어 SwiperSwiperSlide를 가져왔다.

import { Swiper, SwiperSlide } from 'swiper/react';
import 'swiper/css'; //css는 이렇게 가져온다.

3. 기본 틀 만들기

컴포넌트의 리턴 값으로 Swiper와 SwiperSlide를 넣어주었다. 이렇게 하면 화면에는 Slide 1이 떠있고, 슬라이드를 밀면 순서대로 2, 3으로 변한다. 3에서 오른쪽으로 밀어도 1로 되돌아가지는 않았다.

   <Swiper>
      <SwiperSlide>Slide 1</SwiperSlide>
      <SwiperSlide>Slide 2</SwiperSlide>
      <SwiperSlide>Slide 3</SwiperSlide>
    </Swiper>

4. 기본 CSS 설정

앱의 배경은 기본 CSS를 사용하고, SwiperContainer 부터는 styled-conponents를 사용해서 작성했다.

//styles.css
* {
  box-sizing: border-box;
  width: 100%;
  margin: 0;
  padding: 0;
  height: 100%;
}

.App {
  font-family: "Montserrat", sans-serif;
  margin: 0;
  width: 100%;
  height: 100vh;

  background: linear-gradient(
    45deg,
    rgb(153, 205, 227) 30%,
    rgb(139, 163, 209) 40%,
    rgb(123, 122, 191),
    rgb(104, 82, 173),
    rgb(81, 39, 155) 60%
  );
  background-size: 200% 200%;
  animation: gradient 15s ease infinite;
  display: flex;
  justify-content: center;
  align-items: center;
  flex-direction: column;
  padding: 32px;
}
//배경 이미지 위치가 계속 바뀌는 애니메이션
@keyframes gradient {
  0% {
    background-position: 0% 50%;
  }
  50% {
    background-position: 100% 50%;
  }
  100% {
    background-position: 0% 50%;
  }
}

leaner-gradient
https://www.notion.so/leaner-gradient-b4762372a40140ca9753e5ebecfc8bf8

색상 추천 사이트
http://khroma.co/generator/ 마음에 드는 색을 선택하면 추천해준다!
https://webgradients.com/ gradient 사이트

const StyledSwiperContainer = styled.div`
  /* width: 100%;
  height: 100%; */
  background: linear-gradient(
    270deg,
    rgba(247, 249, 255, 1) 0%,
    rgba(242, 246, 255, 1) 100%
  );
  position: relative;
  max-width: 420px;
  max-height: 420px;
  border-radius: 10px;
`;

const StyledSwiper = styled(Swiper)`
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  z-index: 1;
  position: relative;
`;
//styeld-components를 적용해서 리턴 값도 바뀌었다.
 <StyledSwiperContainer>
      <StyledSwiper>
        <SwiperSlide>Slide 1</SwiperSlide>
        <SwiperSlide>Slide 2</SwiperSlide>
        <SwiperSlide>Slide 3</SwiperSlide>
      </StyledSwiper>
    </StyledSwiperContainer>

5. SliderItem Component 만들기

하나의 슬라이드를 나타내기 위한 컴포넌트를 만들었다. 슬라이드마다 이미지와 제목이 다르므로 상위 컴포넌트인 SwiperContainer에서 propstitlesrc를 받아왔다. 지금은 내용을 동일하게 설정했지만, 내용도 바꾸려면 받아와야 한다.

//SliderItem.jsx
const Slideritem = ({ title, src }) => {
  return (
    <SliderItemContainer>
      <SliderImageWrapper>
        <img className="slider-image" src={src} alt="SliderImg" />
      </SliderImageWrapper>
      <SliderItemContent>
        <Title>{title}</Title>
        <Content>
          Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
          eiusmod tempor incididunt ut labore
        </Content>
      </SliderItemContent>
    </SliderItemContainer>
  );
};

export default Slideritem;

각 요소마다 css도 적용해주었다.

//SliderItem.jsx
const SliderItemContainer = styled.div`
  width: 100%;
  height: 100%;
  border-radius: 10px;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  cursor: grab;
`;

const SliderItemContent = styled.div`
  padding: 32px;
  display: flex;
  flex-direction: column;
  justify-content: center;
  transition: 0.4s;
`;

const SliderImageWrapper = styled.div`
  height: 300px;
  width: 100%;
  overflow: hidden;

  > img {
    width: 100%;
    height: 100%;

    /* cover : 대체 콘텐츠의 가로세로비 유지 & 요소 콘텐츠박스를 가득 채움 */
    object-fit: cover;
    transition: 0.2s;
  }
`;

const Title = styled.h1`
  margin: 0;
  font-weight: bold;
  font-size: 24px;
  /* 행간조절 */
  line-height: 32px;
  color: #26384e;
  transform: translateY(20px);
  transition: all 0.4s ease;
  transition-delay: 0.2s;
  overflow: hidden;
  max-width: 100%;
  /* ellipsis '...' */
  text-overflow: ellipsis;
  /* 공백처리 방법. nowrap은 줄바꿈은 1개의 공백으로 바꾸고, 자동 줄바꿈은 지원하지 않음 */
  white-space: nowrap;

  /* 가로 520px까지 작게 */
  @media screen and (max-width: 520px) {
    & {
      font-size: 16px;
      line-height: 24px;
    }
  }
`;

const Content = styled.p`
  font-size: 16px;
  line-height: 24px;
  color: #889db8;
  transform: translateY(20px);
  transition: all 0.4s ease;
  transition-delay: 0.3s;
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
  text-overflow: ellipsis;

  @media screen and (max-width: 520px) {
    & {
      font-size: 14px;
      line-height: 20px;
    }
  }
`;

SwiperContainer에서 불러온 후 map 함수를 이용해서 SwiperSlide 컴포넌트 안에 Slideritem이 나타날 수 있도록 만들었다. 그리고 items라는 변수를 선언해서 제목과 이미지를 할당하고 Slideritem으로 내려주었다.

//SwiperContainer;jsx
const items = [
  {
    title: "Postcards From Italy",
    src:
      "https://images.unsplash.com/photo-1498307833015-e7b400441eb8?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=2600&q=80"
  },
  {
    title: "Bunker",
    src:
      "https://images.unsplash.com/photo-1491900177661-4e1cd2d7cce2?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=2250&q=80"
  },
  {
    title: "Small Mountain",
    src:
      "https://images.unsplash.com/photo-1482192505345-5655af888cc4?ixlib=rb-1.2.1&ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&auto=format&fit=crop&w=2600&q=80"
  }
];

//return 
 {items.map((item, idx) => {
              return (
                <SwiperSlide key={idx}>
                  <Slideritem title={item.title} src={item.src} />
                </SwiperSlide>
              );
            })}

레퍼런스에는 슬라이드 방법이 세 가지로 구현되어 있었다.
1. Navigation
2. Pagination
3. Mouse Wheel
모두 모듈을 사용한 것으로 import 해와야 한다.

6. Navigation 기능

Swiper 공식 문서
https://swiperjs.com/swiper-api#navigation
참고
https://joyful-development.tistory.com/35
https://velog.io/@sohee-k/React-TypeScript-%ED%99%98%EA%B2%BD%EC%97%90%EC%84%9C-Swiper-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0image-slider-library

(swiper는 8.4.5 버전을 사용했다)

공식문서에는 Navigation 사용 방법이 아래와 같이 나와있다.

const swiper = new Swiper('.swiper', {
  navigation: {
    nextEl: '.swiper-button-next', //className을 적어주었다
    prevEl: '.swiper-button-prev',
  },
});

하지만 className을 사용하는 방법은 렌더링되는 컴포넌트에서 잘 작동하지 않아서 useRef를 사용했다.

//SwiperContainer.jsx
import SwipeCore, { Navigation } from "swiper";
import "swiper/css/navigation";

//컴포넌트 안에서 Navigation을 불러온다. 
SwipeCore.use([Navigation]);

const prevRef = useRef(null);
  const nextRef = useRef(null);

  const [swiperSetting, setSwiperSetting] = useState(null);

  useEffect(() => {
    if (!swiperSetting) {
      const settings = {
        navigation: {
          prevEl: prevRef.current, 
          nextEl: nextRef.current 
        },
        onBeforeInit: (swiper) => {
          // 초기설정
          swiper.params.navigation.prevEl = prevRef.current;
          swiper.params.navigation.nextEl = nextRef.current;
        }
      };
      setSwiperSetting(settings);
    }
  }, [swiperSetting]);

useEffect를 사용한 이유

prevRefnextRef에 저장되는 값 때문이다. 만약 settings를 그냥 선언하면 초깃값에 null이 들어간 채로 settings가 선언될 수 있다. 따라서 useEffect를 사용해서 컴포넌트를 렌더링할 때 settings가 선언되어야 값이 잘 들어갈 것이다.

return

//SwiperContainer.jsx
  <StyledNavigations>
      <button  className="swiper-button-prev" ref={prevRef}>
            Prev
      </button>
      <button className="swiper-button-prev" ref={nextRef}>
            Next
      </button>
  </StyledNavigations>

🤔
1. className="swiper-button-prev" 를 사용하지 않았는데 제거하면 위치가 바뀐다.. 아직 이유를 못 찾았다.
2. 레퍼런스에는 StyledNavigationstop이 100%로 설정되어 있는데 똑같이 설정하면 화면 밑으로 나간다..
3. 버튼 위치가 오른쪽으로 가지 않았다.
4. 제일 앞 페이지인 경우 맨 뒤로 이동하도록(반대도) 구현이 덜 되었다.
5. className에 'swiper-slide-active'를 설정하면 css를 적용할 수 있다는데 리액트 컴포넌트에서는 지원하지 않는다고 한다..

css

const StyledNavigations = styled.div`
  position: absolute;
  display: flex;
  top: 55%;
  justify-content: flex-end;
  width: 100%;
  padding-top: 8px;

  > button {
    background-color: transparent;
    border: none;
    cursor: pointer;
    outline: none;
    color: #fff;
    position: relative;
    margin-left: 4px;

    /* 마우스 올리면 밑줄 생성 */
    &:before {
      content: "";
      position: absolute;
      background-color: #fff;
      height: 1px;
      width: 0;
      left: 0;
      /* top: 50%;
      transform: translatey(-50%); */
      bottom: -1px;
      transition: 0.2s;
    }

    &:hover:before {
      width: 100%;
    }
    //너비가 520px이하가 되면 보이지 않는다.
    @media screen and (max-width: 520px) {
      &:hover:before,
      &:hover:before {
        display: none;
      }
    }

    /* < >모양 없애기 */
    ::after {
      display: none;
    }
  }
`;

0개의 댓글