반응형
22.2.27 (일) +35days
캐러셀(Carousel)이란?
캐러셀은 사전적 의미로 회전 목마를 뜻함. 슬라이드 형태로 순환하며 이미지, 영상 등의 콘텐츠를 노출하는 UI를 의미한다.
더 나은 경험을 위한 캐러셀
- autoplay를 사용하지 않는다.
- 사용자가 직접 캐러셀을 컨트롤 하게끔 한다.
- 대부분의 사용자들에게 캐러셀은 일반적인 UI이고 이를 인터페이스 하는데 이미 익숙해져 있다.
- 간결하고 명확한 카피를 작성한다.
- 슬라이더 내부에 h1 태그를 사용하지 않는다.
- 검색엔진이 h1 태그를 제목처럼 취급하기 때문에 슬라이드가 여럿일 경우 한 페이지에 여러 개의 제목을 부여하는 것과 같다.
- 캐러셀이 터치 친화적인지 확인한다.
- 모바일에서 캐러셀을 보고 좌우의 화살표가 아닌 스와이프하여 콘텐츠를 넘기는 것은 너무나 익숙한 사용자 경험이다.
캐러셀 실습
프로젝트 생성
npx create-react-app carousel-playground --template typescript
패키지 설치
npm i @emotion/styled @emotion/react react-icons
dependencies
Carousel.tsx
import { useEffect, useState } from 'react';
import styled from '@emotion/styled/macro';
import { css } from '@emotion/react';
import { RiArrowDropLeftLine, RiArrowDropRightLine } from 'react-icons/ri';
const Base = styled.div``;
const Container = styled.div`
position: relative;
`;
const ArrowButton = styled.button<{ pos: 'left' | 'right' }>`
position: absolute;
top: 50%;
z-index: 1;
padding: 8px 12px;
font-size: 48px;
font-weight: bold;
background-color: transparent;
color: #fff;
border: none;
margin: 0;
cursor: pointer;
${({ pos }) =>
pos === 'left'
? css`
left: 0;
`
: css`
right: 0;
`};
`;
const CarouselList = styled.ul`
list-style: none;
margin: 0;
padding: 0;
display: flex;
overflow: hidden;
`;
const CarouselListItem = styled.li<{ activeIndex: number }>`
width: 100%;
flex: 1 0 100%;
transform: translateX(-${({ activeIndex }) => activeIndex * 100}%);
transition: 200ms ease;
> img {
width: 100%;
height: fit-content;
}
`;
const NavButton = styled.button<{ isActive?: boolean }>`
width: 4px;
height: 4px;
background-color: #000;
opacity: ${({ isActive }) => (isActive ? 0.3 : 0.1)};
`;
const NavItem = styled.li`
display: inline-block;
`;
const Nav = styled.ul`
list-style: none;
padding: 0;
margin: 0 auto;
display: flex;
justify-content: center;
${NavItem} + ${NavItem} {
margin-left: 4px;
}
`;
const banners = [
'https://via.placeholder.com/600/92c952',
'https://via.placeholder.com/600/771796',
'https://via.placeholder.com/600/24f355',
];
export default function Carousel() {
const [activeIndex, setActiveIndex] = useState<number>(0);
const [isFocused, setIsFocused] = useState<boolean>(false);
const handleNext = () => {
setActiveIndex((activeIndex) => (activeIndex + 1) % banners.length);
};
const handlePrev = () =>
setActiveIndex(
(activeIndex) => (activeIndex - 1 + banners.length) % banners.length
);
const handleMouseEnter = () => setIsFocused(true);
const handleMouseLeave = () => setIsFocused(false);
const goTo = (idx: number) => {
setActiveIndex(idx);
};
useEffect(() => {
let intervalId: NodeJS.Timeout;
if (!isFocused) {
intervalId = setInterval(handleNext, 3000);
}
return () => {
clearInterval(intervalId);
};
}, [isFocused]);
return (
<Base onMouseEnter={handleMouseEnter} onMouseLeave={handleMouseLeave}>
<Container>
<ArrowButton pos="left" onClick={handlePrev}>
<RiArrowDropLeftLine />
</ArrowButton>
<CarouselList>
{banners.map((banner, idx) => (
<CarouselListItem activeIndex={activeIndex} key={idx}>
<img src={banner} alt="banner" />
</CarouselListItem>
))}
</CarouselList>
<ArrowButton pos="right" onClick={handleNext}>
<RiArrowDropRightLine />
</ArrowButton>
</Container>
<Nav>
{Array.from({ length: banners.length }).map((_, idx) => (
<NavItem onClick={() => goTo(idx)}>
<NavButton isActive={activeIndex === idx} />
</NavItem>
))}
</Nav>
</Base>
);
}
App.tsx
import React from 'react';
import Carousel from './components/Carousel';
function App() {
return (
<div className="App">
<Carousel />
</div>
);
}
export default App;
본 포스팅은 패스트캠퍼스 환급 챌린지 참여를 위해 작성되었습니다
반응형
댓글