흔들리는 빵 애니메이션 구현하기

이보아·2024년 7월 16일
0

빵이 움직이는 애니메이션 만들기

프로젝트 대빵이를 만드는데 메인 페이지에 들어가는 애니메이션을 구현하였다.

1. 빵 이미지 배열 생성

const breadImages: string[] = [
  "/image/breads/LogoBread.png",
  "/image/breads/bread1.png",
  "/image/breads/bread2.png",
  "/image/breads/bread3.png",
  "/image/breads/bread4.png",
  "/image/breads/bread5.png",
]; 

2. 배열을 섞는 함수 만들기

const shuffleArray = (array: string[]): string[] => {
  let shuffledArray = array.slice();
  for (let i = shuffledArray.length - 1; i > 0; i--) {
    const j = Math.floor(Math.random() * (i + 1));
    [shuffledArray[i], shuffledArray[j]] = [shuffledArray[j], shuffledArray[i]];
  }
  return shuffledArray;
};
  • shuffleArray 함수는 배열을 받아서 요소를 무작위로 섞은 새로운 배열을 반환한다.
  • Fisher-Yates 셔플 알고리즘을 사용하여 랜덤하게 섞는다.

📖Fisher-Yates란?

  • Fisher-Yates 셔플 알고리즘은 배열의 요소를 무작위로 섞는 효율적인 알고리즘이다. 이 알고리즘은 각 요소가 동등한 확률로 선택되도록 보장한다.

원리
1. 배열의 마지막 요소부터 시작하여 첫 번째 요소까지 반복한다.
2. 현재 요소와 그 앞의 요소들 중 무작위로 선택된 요소의 위치를 바꾼다.
3. 이 과정을 배열의 모든 요소에 대해 반복한다.

1,2,3을 통해 배열이 완전히 무작위로 섞인다.

3. BouncingBread 생성

const BouncingBread: React.FC = () => {
  const [shuffledBreadImages1, setShuffledBreadImages1] = useState<string[]>([]);
  const [shuffledBreadImages2, setShuffledBreadImages2] = useState<string[]>([]);
  const [shuffledBreadImages3, setShuffledBreadImages3] = useState<string[]>([]);

이 컴포넌트는 세 개의 섞인 빵(1,2,3줄) 이미지 세트를 관리하기 위해 useState를 사용한다.

4. useEffect

   useEffect(() => {
    setShuffledBreadImages1(shuffleArray(breadImages));
    setShuffledBreadImages2(shuffleArray(breadImages.concat(breadImages).slice(0, 15)));
    setShuffledBreadImages3(shuffleArray(breadImages));
  }, []);
1231

5. 이미지를 렌더링 하는 함수 생성

  const renderImages = (images: string[], top: string, baseLeft: number) =>
    images.map((src, index) => (
      <Image
        key={`${top}-${index}`}
        src={src}
        alt="빵 애니메이션 이미지"
        width={98}
        height={98}
        className="absolute animate-float custom-lg:w-[95px] custom-lg:h-[95px] md-max:w-[80px] md-max:h-[80px] sm-max:w-[65px] sm-max:h-[65px]"
        style={{
          objectFit: "contain",
          left: `calc(${baseLeft}% + ${index * 22}%)`,
          top: top,
        }}
      />
    ));
  • renderImages는 이미지를 특정 스타일로 렌더링하기 위한 함수 생성한다.
  • 이미지 소스 배열을 순회하면서 각 요소에 대해 Image 컴포넌트를 생성하고 스타일과 위치를 동적으로 설정한다.

6.JSX 반환하기

  return (
    <div className="relative w-full h-[300px] bg-[#fff6d9] overflow-hidden border-b border-[#f7ebc4]">
      {renderImages(shuffledBreadImages1, "10%", 10)}
      {renderImages(shuffledBreadImages2, "45%", 0)}
      {renderImages(shuffledBreadImages3, "80%", 10)}
    </div>
  );
};
  • 부모의 요소에 relative와 배경색, 높이와 같은 tailwind css명을 준다.
  • 세 개의 이미지 세트를 서로 다른 수직 위치 (top: "10%", "45%", "80%")에 렌더링하는 div를 반환한다.
  • 각 세트는 baseLeft와 index를 사용하여 수평 위치를 계산하여, 렌더링한다.

7. 빵에 자연스러운 애니메이션 추가

 animation: {
        float: "float 3s ease-in-out infinite",
      },
      smokeRise: {
        "0%": { opacity: "0", transform: "translateY(20px)" },
        "50%": { opacity: "0.7" },
        "100%": { opacity: "0", transform: "translateY(-100px)" },
      },

tailwind.config.ts파일에 animation을 추가하고 classname에 적용한다.

전체 코드

"use client"; 

import Image from "next/image";
import React, { useEffect, useState } from "react"; 

// 사용할 이미지 배열 생성 
const breadImages: string[] = [
  "/image/breads/LogoBread.png",
  "/image/breads/bread1.png",
  "/image/breads/bread2.png",
  "/image/breads/bread3.png",
  "/image/breads/bread4.png",
  "/image/breads/bread5.png",
]; 


// 배열을 랜덤으로 섞는 함수
const shuffleArray = (array: string[]): string[] => {
  let shuffledArray = array.slice(); // 원본 배열의 복사본
  for (let i = shuffledArray.length - 1; i > 0; i--) { // 배열 뒤부터 순회
    const j = Math.floor(Math.random() * (i + 1)); // 랜덤 인덱스를 선택
    [shuffledArray[i], shuffledArray[j]] = [shuffledArray[j], shuffledArray[i]]; // 현재 요소와 랜덤 요소를 교환
  }
  return shuffledArray; // 섞인 배열을 반환
};

const BouncingBread: React.FC = () => { // BouncingBread라는 React 함수형 컴포넌트
  // 첫 번째 섞인 빵 이미지 배열을 저장하는 상태
  const [shuffledBreadImages1, setShuffledBreadImages1] = useState<string[]>([]); // 첫 번째 섞인 빵 이미지 배열을 저장하는 상태
  const [shuffledBreadImages2, setShuffledBreadImages2] = useState<string[]>([]); // 두 번째 섞인 빵 이미지 배열을 저장하는 상태
  const [shuffledBreadImages3, setShuffledBreadImages3] = useState<string[]>([]); // 세 번째 섞인 빵 이미지 배열을 저장하는 상태

  // 컴포넌트가 처음 렌더링될 때 빵 이미지를 섞음
  useEffect(() => {
    setShuffledBreadImages1(shuffleArray(breadImages)); // 첫 번째 빵 이미지 배열을 섞고 설정함
    setShuffledBreadImages2(shuffleArray(breadImages.concat(breadImages).slice(0, 15))); // 긴 빵 이미지 배열을 섞고 설정해요.
    setShuffledBreadImages3(shuffleArray(breadImages)); // 세 번째 빵 이미지 배열을 섞고 설정함
  }, []);

  // 이미지를 화면에 렌더링하는 함수    
  const renderImages = (images: string[], top: string, baseLeft: number) =>
    images.map((src, index) => (
      <Image
        key={`${top}-${index}`} 
        src={src} 
        alt="빵 애니메이션 이미지"
        width={98} 
        height={98} 
        className="absolute animate-float custom-lg:w-[95px] custom-lg:h-[95px] md-max:w-[80px] md-max:h-[80px] sm-max:w-[65px] sm-max:h-[65px]" 
        style={{
          objectFit: "contain", 
          left: `calc(${baseLeft}% + ${index * 22}%)`,
          top: top,123123123  
        }}
      />
    ));

  return (
    <div className="relative w-full h-[300px] bg-[#fff6d9] overflow-hidden border-b border-[#f7ebc4]">
      {/* 첫 번째 줄의 이미지 */}
      {renderImages(shuffledBreadImages1, "10%", 10)}
      {/* 두 번째 줄의 이미지 */}
      {renderImages(shuffledBreadImages2, "45%", 0)}
      {/* 세 번째 줄의 이미지 */}
      {renderImages(shuffledBreadImages3, "80%", 10)}
    </div>
  );
};

export default BouncingBread;
profile
매일매일 틀깨기

0개의 댓글