[ TIL 221214 ] 캐러셀(Carousel)이란?

ponyo·2022년 12월 13일
0

Today I Learned

목록 보기
25/30

캐러셀은 사전적 의미로 회전 목마


더 나은 경험을 위한 캐러셀

  • autoplay를 사용하지 않는다.
  • 사용자가 직접 캐러셀을 컨트롤 하게끔 한다.
  • 간결하고 명확한 카피 작성
  • 슬라이더 내부에 h1 태그를 사용하지 않음
    • 검색엔진이 h1 태그를 제목으로 취급하기 때문
  • 캐러셀이 터치 친화적인지 확인 (스와이프)

Install

yarn add @emotion/styled @emotion/react react-icons

App.tsx

import React from "react";
import Carousel from "./components/Carousel";

function App() {
  return (
    <div>
      <Carousel />
    </div>
  );
}

export default App;

Carousel.tsx

import React, { 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/92c952",
  "https://via.placeholder.com/600/92c952",
  "https://via.placeholder.com/600/92c952",
  "https://via.placeholder.com/600/92c952",
  "https://via.placeholder.com/600/92c952",
];

const Carousel: React.FC = () => {
  const [activeIndex, setActiveIndex] = useState<number>(0);
  const [isFocused, setIsFocused] = useState<boolean>(false);

  const handleNext = () => {
    setActiveIndex((prev) => (prev + 1) % banners.length);
  };

  const handlePrev = () => {
    setActiveIndex((prev) => prev - 1 + 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} />
            </CarouselListItem>
          ))}
        </CarouselList>
        <ArrowButton pos="right" onClick={handleNext}>
          <RiArrowDropRightLine />
        </ArrowButton>
      </Container>
      <Nav>
        {Array.from({ length: banners.length }).map((_, idx) => (
          <NavItem key={idx} onClick={() => goTo(idx)}>
            <NavButton isActive={activeIndex === idx} />
          </NavItem>
        ))}
      </Nav>
    </Base>
  );
};

export default Carousel;

Reference
FastCampus React 강의

profile
😁

0개의 댓글