customPaging 옵션을 이용한 작업방식입니다.
슬라이드 2개로 페이징을 구현하려면 새로 작성한 글을 참고해주세요!
사진첩 작업을 하기위해 react-slick 라이브러리를 활용해보겠습니다.
그냥 slick을 쓴 경험은 있지만 react에서는 강의를 보고 예제 코드 만들어 본 이후 처음이네요.ㅎㅎ
라이브러리 설치를 먼저 해주시구요.
npm i react-slick
대충 테스트해보기위해 slick 사이트에 있는 예제를 가져와서 간단히 뚝딱 만들어보겠습니다.
아래 사이트에서 docs를 확인해보세요.
https://react-slick.neostack.com/
예제코드
확인할때 눈에 보기 쉽게 div에 background를 추가해보았습니다.
import React from 'react';
import Slick from 'react-slick';
import styled from 'styled-components';
const Div = styled.div`
width: 100%;
height: 50px;
background: red;
`;
const Test = () => {
return (
<Slick
dots={true}
infinite
speed={500}
slidesToShow={1}
slidesToScroll={1}
>
<Div>
<h3>1</h3>
</Div>
<Div>
<h3>2</h3>
</Div>
<Div>
<h3>3</h3>
</Div>
<Div>
<h3>4</h3>
</Div>
<Div>
<h3>5</h3>
</Div>
<Div>
<h3>6</h3>
</Div>
</Slick>
);
};
export default Test;
그 후 결과물을 보고 당황했습니다;
아래처럼 정상적으로 나오지 않더군요.
뭐가 문제냐..
음.. 스타일을 수정해주면 됩니다.
객체들이 정렬될 수 있게 display를 수정해줍니다.
이 상태에서 slick이 부여한 스타일을 덮여씌우려면 글로벌 스타일을 통해 작업이 필요할 것 같습니다.
styled-components에서 제공하는 createGlobalStyle를 이용해서 스타일을 작성해줍니다.
그리고 이 Global 컴포넌트를 사용하면 됩니다.
import styled, { createGlobalStyle } from 'styled-components';
const Global = createGlobalStyle`
.slick-slide {
display: inline-block;
}
`;
정상적으로 보이네요.
뭐 지금도 1 다음에 2가 보이긴 하지만요;
저것도 스타일로 해결 가능합니다.
여튼,
간단히 예제코드를 확인했으니 어떻게 하면 되는지 머리에 좀 그려졌습니다.
이제 원하는 슬라이드로 만들기 위해 스타일이나 react-slick의 속성을 사용해 코드를 작성해보겠습니다.
설명은 주석을 통해 작성하겠습니다. 연관된 설명은 번호가 동일합니다.
import React, { useRef } from 'react';
import Slick from 'react-slick';
import styled, { css } from 'styled-components';
import { LeftOutlined, RightOutlined } from '@ant-design/icons';
const Wrap = styled.div`
position: relative;
padding-bottom: 70px;
overflow: hidden;
// 1. Global style 추가했던 것을 슬라이드 상단에 Wrap을 만들어 여기서 선언했습니다.
.slick-slide {
display: inline-block;
}
// 2. 제가 추가한 커스텀 클래스입니다.
// pagination 부분입니다.
.slick-dots.slick-thumb {
position: absolute;
bottom: 0;
left: 50%;
padding: 0;
margin: 0;
list-style: none;
transform: translate(-50%);
li {
position: relative;
display: inline-block;
&.slick-active {
span {
filter: none;
}
}
}
}
`;
const SlickItems = styled.div`
width: 100%;
height: 400px;
text-align: center;
img {
max-width: 100%;
height: 100%;
vertical-align: top;
}
`;
const defaultButtonStyle = css`
position: absolute;
top: calc(50% - 50px);
padding: 0;
width: 30px;
height: 30px;
line-height: 1;
border: none;
border-radius: 50%;
background: none;
outline: none;
cursor: pointer;
`;
const PrevButton = styled.button`
${defaultButtonStyle}
left: 0;
`;
const NextButton = styled.button`
${defaultButtonStyle}
right: 0;
`;
const defaultIconStyle = css`
font-size: 22px;
color: #dedede;
&:focus,
&:hover {
color: #666;
}
`;
const PrevIcon = styled(LeftOutlined)`
${defaultIconStyle}
`;
const NextIcon = styled(RightOutlined)`
${defaultIconStyle}
`;
const PagingAnchor = styled.a`
display: block;
width: 50px;
height: 50px;
img {
width: 100%;
height: 100%;
}
`;
// 3. custom pagination을 만듭니다.
// background를 통해 이미지를 넣어줍니다.
// filter를 통해 흑백으로 보이게 하고 active가 되면 흑백을 제거합니다. (31라인참고)
const Paging = styled.span`
display: inline-block;
width: 100%;
height: 100%;
vertical-align: middle;
background: url(${props => props.src})no-repeat;
background-size: 100% 100%;
filter: grayscale(1);
`;
// 4. 샘플이미지
const images = [
{
src: "https://www.artinsight.co.kr/data/tmp/1910/20191029212614_fawslbwd.jpg",
title: "1"
},
{
src: "https://www.artinsight.co.kr/data/tmp/1910/20191029212649_esiekzxf.jpg",
title: "2"
},
{
src: "https://www.artinsight.co.kr/data/tmp/1910/20191029212707_zcrkccgp.jpg",
title: "3"
},
{
src: "https://www.artinsight.co.kr/data/tmp/1910/20191029212724_pacwfbiz.jpg",
title: "4"
},
];
const Slide = () => {
// 5. custom arrows를 만들어 ref를 통해 제어합니다.
const slickRef = useRef(null);
// 6. slick에 추가할 세팅입니다.
const settings = {
dots: true,
// 2. 제가 추가한 커스텀 클래스입니다. (pagination)
dotsClass: "slick-dots slick-thumb",
// 5. custom arrows를 만들기 위해 기본 arrows옵션을 false로 합니다.
arrows: false,
infinite: true,
slidesToShow: 1,
slidesToScroll: 1,
// 2. custom pagination을 만듭니다.
// i(index)를 통해 샘플이미지에서 동일한 이미지를 가져옵니다.
customPaging: function(i) {
const imgSrc = images[i].src;
return (
<PagingAnchor>
<Paging src={imgSrc} />
</PagingAnchor>
);
},
};
// 5. custom arrows 동작 함수를 만듭니다.
const previous = useCallback(() => slickRef.current.slickPrev(), []);
const next = useCallback(() => slickRef.current.slickNext(), []);
return (
<Wrap>
// 5.6.
// custom arrows를 위해 ref를 설정합니다.
// 세팅을 넣어줍니다. (공식문서 docs참고)
<Slick ref={slickRef} {...settings}>
// 4. 샘플이미지로 반복문을 돌려 슬라이드 아이템을 렌더합니다.
{images.map((v, i) => {
return (
<SlickItems key={`${v.title}_${i}`}>
<img src={v.src} />
</SlickItems>
)
})}
</Slick>
// 5. custom arrows입니다.
<>
<PrevButton onClick={previous}>
<PrevIcon />
<span className="hidden">이전</span>
</PrevButton>
<NextButton onClick={next}>
<NextIcon />
<span className="hidden">다음</span>
</NextButton>
</>
</Wrap>
);
};
export default Slide;
이미지 출처는 https://www.artinsight.co.kr/인 것 같습니다. 사실 뉴스 검색해서 사용한 이미지인데, 주소가 저기로 되어있습니다. 문제가되면 수정하겠습니다.
결과물입니다.
아직 간단히 만든 상태라 버그가 존재합니다.
예를들어.. 이미지 개수가 왕창 늘어나면? 아래 pagination은 깨질 것입니다.
그것 말고도 만지다보면 많이 있겠죠.
저는 딱 정해진 갯수의 사진만 쓸거기에 수정하지 않겠지만, 동적으로 이미지 갯수가 달라진다면 수정해야합니다.
gif
용량때문에 급하게 누르느라 gif가 엄청빠르네요;;;
이번시간에 만든 슬라이드는
화살표, 페이지 번호를 사용자 요구조건으로 수정하는 기능만 적용했습니다.
간단합니다.
하지만 이 기능 구현하면서 docs를 읽어봤는데, 대부분의 기능은 docs를 보면 만들 수 있을 것 같습니다.(우와)
bug
antd 라이브러리의 card 컴포넌트와 slick을 같이 사용한다면, 버그가 생깁니다.
버그를 해결하기 위해 global에 스타일 하나를 수정해줘야합니다.
꼭 global일 필요는 없으나 .ant-card-cover를 감싸고 있는 element면 됩니다.
글로벌 스타일 적용방법을 참고해서 아래 코드를 추가해주세요.
const Global = createGlobalStyle`
.ant-card-cover {
transform: none !important;
}
`;