본문 바로가기
챌린지/패스트캠퍼스 공부 루틴 챌린지

패스트캠퍼스 챌린지 35일차 (Carousel)

by 무벅 2022. 2. 27.
반응형

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;

 

 

 

 

 

 

https://bit.ly/37BpXiC

본 포스팅은 패스트캠퍼스 환급 챌린지 참여를 위해 작성되었습니다

 

 

 

 

 

반응형

댓글