캐로셀
Slick 라이브러리
목 데이터로 reviewAll 데이터를 훅으로 생성해서 사용한다.
간단한 슬라이딩 효과
`npm i react-slick --save` `npm i slick-carousel --save` 타입 설치 `npm i --save-dev @types/react-slick`
import Slider from 'react-slick';
import 'slick-carousel/slick/slick.css';
import 'slick-carousel/slick/slick-theme.css';
//..
const sliderSettings = {
dots: true,
infinite: true,
speed: 500,
slidesToShow: 3,
slidesToScroll: 3,
gap: 16,
};
//..
<Slider {...sliderSettings}>
//..
</Slider>
리액트 컴포넌트 직접 구현
타입 생성 시 target 항목 필수!
export interface Banner {
id: number;
title: string;
description: string;
image: string;
url: string;
target: string;
}
picsum 에서 목 데이터 이미지를 가져온다.
target에서 `_balnk` 새 탭에서 열겠다. `_self` 현재 탭에서 열겠다 옵션!
position을 relative, absolute 를 적절하게 주어, 이미지 상단에 콘텐츠title, description 이 들어가도록 한다.
슬라이드 구현
currentIndex로 슬라이드 순서 구별.
`flex: 0 0 100%;` 으로 화면에 100%로 보이면서, 정렬은 column 으로 되는 효과!
다음, 이전 버튼
const handlePrev = () => {
if (currentIndex === 0) return;
setCurrentIndex(currentIndex - 1);
};
const handleNext = () => {
if (currentIndex === banners.length - 1) return;
setCurrentIndex(currentIndex + 1);
};
배너 이동하는 transform 값은 인덱스로 이동해야할 만큼 계산하고
css 에서 이동해준다. 애니메이션도 같이!
const [currentIndex, setCurrentIndex] = useState(0);
const transFormValue = useMemo(() => {
return currentIndex * -100;
}, [currentIndex]);
//..
const BannerContainerSytle = styled.div<BannerContainerSytleProps>`
display: flex;
transform: translateX(${(props) => props.$transFormValue}%);
transition: transform 0.5s ease-in-out;
`;
인디케이터 Indecator
스타일과 클래스명 active로 스타일링을 하고, onClick으로 setCurrentIndex를 index 로 설정해서 이동하도록 한다.
const handleIndecatorClick = (index: number) => {
setCurrentIndex(index);
};
//..
<BannerIndecatorStyle>
{banners.map((_, index) => (
<span
className={index === currentIndex ? 'active' : ''}
onClick={() => handleIndecatorClick(index)}
></span>
))}
</BannerIndecatorStyle>
//..
const BannerIndecatorStyle = styled.div`
position: absolute;
bottom: 10px;
left: 50%;
transform: translateX(-50%);
span {
display: inline-block;
width: 16px;
height: 16px;
border-radius: 100px;
background: #fff;
margin: 0 4px;
cursor: pointer;
&.active {
background: ${({ theme }) => theme.color.primary};
}
}
`;
정리
- overflowhidden 을 가지는 전체 너비를 가지는 뷰포트를
- flex column 정렬 시키고, `flex-grow` `flex-shrink` 를 0으로 설정해서 늘어나지도, 줄어들지도 않게 하기! `flex: 0 0 100%;` flex-basis를 100%로 좌우를 꽉 차게 구성한다. > 캐러셀에서도 조정할 수 있음
- 배너를 이동하게 하는건 배너의 핸들러 transFormValue, currentIndex의 값만 가지고, 값을 반환해서 css 에서 그 값으로 이동하도록 `transform` 옵셕으로 설정하였다.
- 모든 기준은 currentIndex로 기준 역할을 한다.
- transFormValue 에서 오른쪽으로가는 버튼을 누르면, 슬라이드는 사실 왼쪽으로 100%만큼씩 이동하게 된다. 반대여서 -가 붙는다.
- 좌우 버튼은 index 를 조정하는 역할
- indecator 인디케이터는 각 원에 index 를 직접 넣어서, 같은 state(currentIndex)를 동작하도록 하였다.
컴포넌트 재사용 + 일부요소 제외
기존 bookItem을 매핑하는 컴포넌트 bookBestItem 컴포넌트 생성
기존 css를 export 해서, 선택자 할 요소를 지정한다. > `override`를 하겠다!
const BookBestItem = ({ book, itemIndex }: Props) => {
return (
<BookBestItemStyle>
<BookItem book={book} view="grid" />
<div className="rank">{itemIndex + 1}</div>
</BookBestItemStyle>
);
};
const BookBestItemStyle = styled.div`
${BookItemStyle} {
.summary,
.price,
.likes {
display: none;
}
}
모바일 대응 : 반응형 웹
- viewport
- index.html 에서 `<meta name="viewport" content="width=device-width, initial-scale=1" />` 으로 기본적으로 지원해주고 있다.
- 상대값을 가진 레이아웃
- 상대값의 너비는 max-width: 1020px;
- width는 100%로 상태값으로 주고,
- max-width를 주어 고정값을 준다.
- 화면 너비 감지 mediaquery
- 너비를 감지하는 도구
너비 감지 훅
`window.matchMedia.matches` 로 현재 뷰포트의 너비를 분석한다.
export const useMediaQuery = () => {
const [isMobile, setIsMobile] = useState(
window.matchMedia(getTheme('light').mediaQuery.mobile).matches,
);
useEffect(() => {
const isMobileQuery = window.matchMedia(
getTheme('light').mediaQuery.mobile,
);
setIsMobile(isMobileQuery.matches);
}, []);
return { isMobile };
};
모바일 뷰 일 경우, 다음처럼 조건을 나누어 스타일링을 분리할 수 있다.
@media screen And ${({ theme }) => theme.mediaQuery.mobile} {
..//
}
isOpen 값을 상태로 두어서, 열고 닫힐때 스타일링을 분기해 표시한다.
`const [isMobileOpen, setIsMobileOpen] = useState(false);`