프론트엔드/CSS

[CSS] Framer motion을 이용한 슬롯 머신 구현

순코딩 2023. 11. 20. 16:29
기능

배열에 있는 모든 요소를 순회하며 슬롯 머신 처럼 애니메이팅 한다.

 

시연 영상

결정장애 이제 그만! - Chrome 2023-11-20 16-28-51.mp4
19.80MB

 

사용 방법

1. styled-components 설치

npm install styled-components

2. framer-motion 설치

npm install framer-motion

3. SlotMachine.jsx 파일 생성 후 아래 코드 복붙

import React, { useRef, useState } from "react";
import { motion, AnimatePresence } from "framer-motion";
import styled from "styled-components";

const SlotMachine = () => {
  // useRef를 사용하여 motion.div의 DOM 요소에 접근하는 참조 생성
  const slotItemRef = useRef(null);

  // 슬롯에 표시될 문자열을 담은 배열
  const arr = ['사과','포도','딸기','배','복숭아','수박','멜론','망고'];

  // 현재 보여지고 있는 슬롯의 인덱스를 나타내는 상태 변수
  const [currentIndex, setCurrentIndex] = useState(0);

  // 애니메이션이 완료되면  호출되는 함수
  const onAnimationComplete = () => {
    // 다음 슬롯으로 이동하기 위해 currentIndex 업데이트
    setCurrentIndex((prev) => (prev + 1) % arr.length);
  };

  // Framer motion 애니메이션에 필요한 속성값 객체
  const [slotVariants, setSlotVariants] = useState({
    initial: { opacity: 1, y: 40 }, // 텍스트는 밑에서 시작해서
    animate: { opacity: 0.7, y: -40 }, // 위로 올라온다
    transition: { duration: 0.15, times: [0, 1] }, // initial 상태에서 animate 상태까지 도달하는 데에 걸리는 시간은 duration
  });

  // 슬롯 멈추기 함수
  const slotStop = () => {
    changeSlotSpeed(0, 0.5); // 속도 줄이기 1
    changeSlotSpeed(2000, 1); // 속도 줄이기 2
    changeSlotSpeed(4000, 2); // 속도 줄이기 3
    setTimeout(() => {
      setSlotVariants(() => ({})); // 슬롯 멈추기(애니메이션 속성 객체를 모두 초기화 시켜 슬롯을 멈춘다.)
    }, 8200);
  };

  // 슬롯 속도 변화 함수
  const changeSlotSpeed = (delay, speed) => {
    setTimeout(() => {
      setSlotVariants((prev) => ({
        ...prev,
        transition: { duration: speed, times: [0, 1] },
      }));
    }, delay);
  };

  return (
    <>
      <Container>
        {/* 슬롯 컨테이너 */}
        <SlotContainer>
          {/* 애니메이션의 등장,퇴장 감지 / onAnimationComplete을 사용하려면 필요함 */}
          <AnimatePresence>
            {/* 애니메이션 박스 */}
            <motion.div
              key={currentIndex} // 현재 슬롯의 인덱스를 키로 사용하여 애니메이션 처리
              ref={slotItemRef}
              variants={slotVariants}
              initial={slotVariants.initial}
              animate={slotVariants.animate}
              transition={slotVariants.transition}
              // 애니메이션이 완료될 때 호출되는 함수 지정
              onAnimationComplete={onAnimationComplete}
            >
              {/* 슬롯 아이템 보이는 곳 */}
              <SlotItemBox>{arr[currentIndex]}</SlotItemBox>
            </motion.div>
          </AnimatePresence>
        </SlotContainer>
        {/* 멈추기 버튼, 클릭 이벤트 핸들러 추가 필요 */}
        <StopButton onClick={slotStop}>멈춰</StopButton>
      </Container>
    </>
  );
};

const Container = styled.div`
  width: 420px;
  height: 820px;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
`;

// 슬롯을 감싸는 스타일이 적용된 컨테이너 스타일링
const SlotContainer = styled.div`
  width: 200px;
  height: 50px;
  display: flex;
  justify-content: center;
  align-items: center;
  overflow: hidden;
  border: 1px solid black;
  background-color: black;
`;

const SlotItemBox = styled.div`
  color: white;
  font-size: 20px;
  font-weight: bold;
`;

const StopButton = styled.button`
  background-color: #bcd2a1;
  color: white;
  font-size: 15px;
  font-weight: bold;
  width: 200px;
  &:hover {
    background-color: #bcd2a1;
    opacity: 0.9;
  }
`;

export default SlotMachine;