Swiper
, Slick
과 같은 라이브러리를 통해 슬라이드를 쉽게 구현할 순 있지만 이번엔 라이브러리를 사용하지 않고 슬라이드를 구현해 보고 싶었다.
우선 기본 구조는 다음과 같다.
<div className={'main-img-section'}>
<ul ref={ref} className='slider-list'>
{products.results && products.results.map((value, index) => (
<li key={index}>
<img src={value.image} alt={value.product_info}/>
</li>
))}
</ul>
<button onClick={prevButtonClick} className='btn-arrow btn-prev'></button>
<button onClick={nextButtonClick} className='btn-arrow btn-next'></button>
</div>
.main-img-section {
width: 1280px; height: 500px;
margin: 60px auto;
overflow: hidden;
position: relative;
border-radius: 10px;
}
.slider-list {
display: flex;
transition: 0.5s;
}
.slider-list li {
width: 1280px;
}
.slider-list li img {
width: 1280px;
height: 500px;
}
.btn-arrow {
border: none;
cursor: pointer;
position: absolute;
top: 50%;
width: 100px; height: 100px;
transform: translate(0, -50%);
}
.btn-prev {
left: 20px;
background: transparent url('./assets/icon-swiper-1.svg') no-repeat center;
}
.btn-next {
right: 20px;
background: transparent url('./assets/icon-swiper-2.svg') no-repeat center;
}
중요한 점은 슬라이더를 감싸는 가장 큰 부모구역과 개당 이미지(li
)의 크기를 같게 하는 것이다.
가장 큰 부모구역에서 overflow: hidden
과 ul
태그에선 display: flex
의 코드를 통해 가로로 정렬해 주었다.
이제 슬라이더를 하기 위한 기본 구조는 끝마쳤다.
사용자가 원하는 방향으로 슬라이드를 넘기기 위한 작업이 필요하다.
현재 화면에 보여지고 있는 슬라이드의 인덱싱을 바탕으로 해당 인덱스에 맞는 이미지를 나타내 주면 된다.
const ref = useRef();
const [currentSlide, setCurrentSlide] = useState(0);
useEffect(() => {
ref.current.style.marginLeft = `${-currentSlide * 1280}px`;
}, [currentSlide])
...
<ul ref={ref} className='slider-list'>
...
</ul>
useRef Hooks
를 통해 태그접근을 허용하고 currentSlide
값이 변할 때마다 margin
값이 변하는 코드이다.
그럼 이제 버튼을 클릭할 때 마다 currentSlide
값을 하나씩 줄이거나 늘리면 될 것 같다.
그 전에 버튼을 클릭할 때 유효성 검사를 진행해야 한다.
예를 들면, 가장 첫 번째 이미지가 화면에 보인다면 왼쪽 버튼 클릭할 때에는 아무 일도 일어나면 안될 테고, 가장 마지막 이미지가 화면에 보인다면 오른쪽 버튼 클릭 시 아무 일도 일어나면 안될 것이다.
const [currentSlide, setCurrentSlide] = useState(0);
const [slideLength, setSlideLength] = useState(0);
const isCheckActivePrevbutton = useCallback(() => {
return currentSlide >= 1
}, [currentSlide])
const isCheckActiveNextbutton= useCallback(() => {
return currentSlide < slideLength - 1
}, [currentSlide, slideLength])
slideLength
값은 이미지 값을 불러올 때 다음과 같이 초기화 했다.
setSlideLength(data.results.length)
각각 유효성 검사 역할을 맡는 함수가 return
하는 boolean
값을 통해 버튼 클릭의 여부를 결정해준다.
이제 위에서 모든 준비를 했기 때문에 좌우 버튼 클릭 시 유효성 검사를 진행하고 currentSlide
값을 변경해면 된다.
const prevButtonClick = useCallback(() => {
if(!isCheckActivePrevbutton()) return
setCurrentSlide(prev => prev - 1)
}, [isCheckActivePrevbutton])
const nextButtonClick = useCallback(() => {
if (!isCheckActiveNextbutton()) return
setCurrentSlide(prev => prev + 1)
}, [isCheckActiveNextbutton])
<button onClick={prevButtonClick} className='btn-arrow btn-prev'></button>
<button onClick={nextButtonClick} className='btn-arrow btn-next'></button>
변경된 currentSlide
값이 변할 때마다 useEffect
가 동작하고 ul
태그의 margin
값이 변하면서 사용자의 화면엔 이미지가 하나씩 보이게 된다.
useEffect(() => {
ref.current.style.marginLeft = `${-currentSlide * 100}%`;
}, [currentSlide])
import { useState, useEffect, useCallback, useRef } from 'react';
export const Main = () => {
const ref = useRef();
const [currentSlide, setCurrentSlide] = useState(0);
const [slideLength, setSlideLength] = useState(0);
useEffect(() => {
(async () => {
const { data } = ...
setSlideLength(data.results.length)
})()
}, [])
useEffect(() => {
ref.current.style.marginLeft = `${-currentSlide * 1280}px`;
}, [currentSlide])
const isCheckActivePrevbutton = useCallback(() => {
return currentSlide >= 1
}, [currentSlide])
const isCheckActiveNextbutton= useCallback(() => {
return currentSlide < slideLength - 1
}, [currentSlide, slideLength])
const prevButtonClick = useCallback(() => {
if(!isCheckActivePrevbutton()) return
setCurrentSlide(prev => prev - 1)
}, [isCheckActivePrevbutton])
const nextButtonClick = useCallback(() => {
if (!isCheckActiveNextbutton()) return
setCurrentSlide(prev => prev + 1)
}, [isCheckActiveNextbutton])
return(
<div>
<div className={'main-img-section'}>
<ul ref={ref} className='slider-list'>
{products.results && products.results.map((value, index) => (
<li key={index}>
<img src={value.image} alt={value.product_info}/>
</li>
))}
</ul>
<button onClick={prevButtonClick} className='btn-arrow btn-prev'></button>
<button onClick={nextButtonClick} className='btn-arrow btn-next'></button>
</div>
...
</div>
)
}
사실 styled-compoenets
로 슬라이드를 만들어도 위의 동작 원리는 변하지 않는다. 다만 styled-components
를 통해 CSS-in-JS
를 구현해 보고 싶었다.
위의 코드에서 currentSlide
를 통해 유효성 검사를 진행하고 return
된 boolean
값으로 버튼의 투명도를 변경해줌으로써
사용자가 보기 편하게 만들어 보겠다.
먼저 styled-components
를 설치해 준다.
npm i styled-components
yarn add styled-components
button
태그의 스타일은 다음과 같이 정의해준다.
const PrevButton = styledComponents(PrevArrow)`
position: absolute;
top: 50%; left: 12px;
cursor: pointer;
fill-opacity: ${({ active }) => active ? ".9" : ".2"};
`;
const NextButton = styledComponents(NextArrow)`
position: absolute;
top: 50%; right: 12px;
cursor: pointer;
fill-opacity: ${({ active }) => active ? ".9" : ".2"};
`;
이제 위의 태그에 props
로 active
값을 boolean
값으로 전달해주기만 하면 된다.
<PrevButton onClick={prevButtonClick} active={currentSlide >= 1 ? 1 : 0}></PrevButton>
<NextButton onClick={nextButtonClick} active={slideLength && currentSlide < slideLength - 1 ? 1 : 0}></NextButton>
1과 0은 각각 true
와 false
로 반환되면서 active
로 boolean
값이 전달될 것이다.