프로젝트 대빵이를 만드는데 메인 페이지에 들어가는 애니메이션을 구현하였다.
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;
};
shuffleArray
함수는 배열을 받아서 요소를 무작위로 섞은 새로운 배열을 반환한다.- Fisher-Yates 셔플 알고리즘을 사용하여 랜덤하게 섞는다.
📖Fisher-Yates란?
원리
1. 배열의 마지막 요소부터 시작하여 첫 번째 요소까지 반복한다.
2. 현재 요소와 그 앞의 요소들 중 무작위로 선택된 요소의 위치를 바꾼다.
3. 이 과정을 배열의 모든 요소에 대해 반복한다.
1,2,3을 통해 배열이 완전히 무작위로 섞인다.
const BouncingBread: React.FC = () => {
const [shuffledBreadImages1, setShuffledBreadImages1] = useState<string[]>([]);
const [shuffledBreadImages2, setShuffledBreadImages2] = useState<string[]>([]);
const [shuffledBreadImages3, setShuffledBreadImages3] = useState<string[]>([]);
이 컴포넌트는 세 개의 섞인 빵(1,2,3줄) 이미지 세트를 관리하기 위해 useState를 사용한다.
useEffect(() => {
setShuffledBreadImages1(shuffleArray(breadImages));
setShuffledBreadImages2(shuffleArray(breadImages.concat(breadImages).slice(0, 15)));
setShuffledBreadImages3(shuffleArray(breadImages));
}, []);
1231
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 컴포넌트를 생성하고 스타일과 위치를 동적으로 설정한다.
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를 사용하여 수평 위치를 계산하여, 렌더링한다.
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;