저번 주와 마찬가지로 javascript
로 만들어진 카드 슬라이더를 리액트로 클론해보았다. 오늘은 navigation 기능까지 구현했다.
Swiper API
https://swiperjs.com/react
우선 Swiper라는 라이브러리를 사용할 것이므로 설치해주어야 한다.
npm i swiper
Swiper.jsx
라는 컴포넌트를 만들어 Swiper
와 SwiperSlide
를 가져왔다.
import { Swiper, SwiperSlide } from 'swiper/react';
import 'swiper/css'; //css는 이렇게 가져온다.
컴포넌트의 리턴 값으로 Swiper와 SwiperSlide를 넣어주었다. 이렇게 하면 화면에는 Slide 1이 떠있고, 슬라이드를 밀면 순서대로 2, 3으로 변한다. 3에서 오른쪽으로 밀어도 1로 되돌아가지는 않았다.
<Swiper>
<SwiperSlide>Slide 1</SwiperSlide>
<SwiperSlide>Slide 2</SwiperSlide>
<SwiperSlide>Slide 3</SwiperSlide>
</Swiper>
앱의 배경은 기본 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>
하나의 슬라이드를 나타내기 위한 컴포넌트를 만들었다. 슬라이드마다 이미지와 제목이 다르므로 상위 컴포넌트인 SwiperContainer
에서 props
로 title
과 src
를 받아왔다. 지금은 내용을 동일하게 설정했지만, 내용도 바꾸려면 받아와야 한다.
//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
해와야 한다.
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
를 사용한 이유prevRef
와 nextRef
에 저장되는 값 때문이다. 만약 settings
를 그냥 선언하면 초깃값에 null
이 들어간 채로 settings
가 선언될 수 있다. 따라서 useEffect
를 사용해서 컴포넌트를 렌더링할 때 settings
가 선언되어야 값이 잘 들어갈 것이다.
//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. 레퍼런스에는StyledNavigations
의top
이 100%로 설정되어 있는데 똑같이 설정하면 화면 밑으로 나간다..
3. 버튼 위치가 오른쪽으로 가지 않았다.
4. 제일 앞 페이지인 경우 맨 뒤로 이동하도록(반대도) 구현이 덜 되었다.
5. className에 'swiper-slide-active'를 설정하면 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;
}
}
`;