CASTAD-v0.1/components/SlideshowBackground.tsx

222 lines
7.7 KiB
TypeScript

import React, { useEffect, useState } from 'react';
import { TransitionEffect } from '../types';
interface SlideshowBackgroundProps {
images: string[];
durationPerImage?: number;
transitionDuration?: number;
effect?: TransitionEffect; // 선택된 효과
}
const EFFECTS_MAP: Record<TransitionEffect, string[]> = {
'Mix': ['fade', 'blur-fade'],
'Zoom': ['zoom-in', 'zoom-out', 'ken-burns-extreme'],
'Slide': ['slide-left', 'slide-right', 'slide-up', 'slide-down'],
'Wipe': ['pixelate-sim', 'flash', 'glitch'] // Wipe 대신 화려한 효과들을 매핑
};
const ALL_EFFECTS = [
'fade',
'slide-left', 'slide-right', 'slide-up', 'slide-down',
'zoom-in', 'zoom-out', 'zoom-spin',
'flip-x', 'flip-y',
'blur-fade',
'glitch',
'ken-burns-extreme',
'pixelate-sim',
'flash'
];
/**
* 사용자 선택에 따른 전환 효과를 적용한 슬라이드쇼 컴포넌트
*/
const SlideshowBackground: React.FC<SlideshowBackgroundProps> = ({
images,
durationPerImage = 6000,
transitionDuration = 1500,
effect = 'Mix' // 기본값 Mix
}) => {
const [activeIndex, setActiveIndex] = useState(0);
const [currentAnimName, setCurrentAnimName] = useState('fade');
// 효과 매핑 로직
const getNextEffect = () => {
const candidates = EFFECTS_MAP[effect] || EFFECTS_MAP['Mix'];
return candidates[Math.floor(Math.random() * candidates.length)];
};
useEffect(() => {
if (images.length <= 1) return;
// 초기 효과 설정
setCurrentAnimName(getNextEffect());
const interval = setInterval(() => {
const nextIndex = (activeIndex + 1) % images.length;
setCurrentAnimName(getNextEffect()); // 다음 효과 랜덤 선택 (카테고리 내에서)
setActiveIndex(nextIndex);
}, durationPerImage);
return () => clearInterval(interval);
}, [activeIndex, images.length, durationPerImage, effect]);
if (!images || images.length === 0) return <div className="w-full h-full bg-black" />;
return (
<div className="absolute inset-0 w-full h-full overflow-hidden bg-black perspective-1000">
{images.map((src, index) => {
const isActive = index === activeIndex;
const isPrev = index === (activeIndex - 1 + images.length) % images.length;
let effectClass = '';
if (isActive) {
effectClass = `animate-${currentAnimName}-in`;
} else if (isPrev) {
effectClass = `animate-fade-out`;
}
return (
<div
key={`${src}-${index}`}
className={`absolute inset-0 w-full h-full
${isActive ? 'z-20 opacity-100' : isPrev ? 'z-10 opacity-100' : 'z-0 opacity-0'}
${effectClass}
`}
style={{
animationDuration: `${transitionDuration}ms`,
animationFillMode: 'forwards',
transformOrigin: 'center center'
}}
>
<img
src={src}
alt={`Slide ${index}`}
className="w-full h-full object-cover"
style={{ objectPosition: 'center' }}
/>
{/* 텍스트 가독성을 위한 오버레이 */}
<div className="absolute inset-0 bg-black/30 pointer-events-none" />
</div>
);
})}
<style>{`
.perspective-1000 { perspective: 1000px; }
/* === Common Out Animation === */
@keyframes fade-out {
0% { opacity: 1; }
100% { opacity: 0; }
}
.animate-fade-out { animation-name: fade-out; }
/* === 1. Fade === */
@keyframes fade-in {
0% { opacity: 0; }
100% { opacity: 1; }
}
.animate-fade-in { animation-name: fade-in; }
/* === 2-5. Slides === */
@keyframes slide-left-in {
0% { transform: translateX(100%); opacity: 0; }
100% { transform: translateX(0); opacity: 1; }
}
.animate-slide-left-in { animation-name: slide-left-in; }
@keyframes slide-right-in {
0% { transform: translateX(-100%); opacity: 0; }
100% { transform: translateX(0); opacity: 1; }
}
.animate-slide-right-in { animation-name: slide-right-in; }
@keyframes slide-up-in {
0% { transform: translateY(100%); opacity: 0; }
100% { transform: translateY(0); opacity: 1; }
}
.animate-slide-up-in { animation-name: slide-up-in; }
@keyframes slide-down-in {
0% { transform: translateY(-100%); opacity: 0; }
100% { transform: translateY(0); opacity: 1; }
}
.animate-slide-down-in { animation-name: slide-down-in; }
/* === 6-8. Zooms === */
@keyframes zoom-in-in {
0% { transform: scale(0.5); opacity: 0; }
100% { transform: scale(1); opacity: 1; }
}
.animate-zoom-in-in { animation-name: zoom-in-in; }
@keyframes zoom-out-in {
0% { transform: scale(1.5); opacity: 0; }
100% { transform: scale(1); opacity: 1; }
}
.animate-zoom-out-in { animation-name: zoom-out-in; }
@keyframes zoom-spin-in {
0% { transform: scale(0) rotate(-180deg); opacity: 0; }
100% { transform: scale(1) rotate(0deg); opacity: 1; }
}
.animate-zoom-spin-in { animation-name: zoom-spin-in; }
/* === 9-10. Flips (3D) === */
@keyframes flip-x-in {
0% { transform: perspective(400px) rotateX(90deg); opacity: 0; }
100% { transform: perspective(400px) rotateX(0deg); opacity: 1; }
}
.animate-flip-x-in { animation-name: flip-x-in; }
@keyframes flip-y-in {
0% { transform: perspective(400px) rotateY(90deg); opacity: 0; }
100% { transform: perspective(400px) rotateY(0deg); opacity: 1; }
}
.animate-flip-y-in { animation-name: flip-y-in; }
/* === 11. Blur Fade === */
@keyframes blur-fade-in {
0% { filter: blur(20px); opacity: 0; transform: scale(1.1); }
100% { filter: blur(0px); opacity: 1; transform: scale(1); }
}
.animate-blur-fade-in { animation-name: blur-fade-in; }
/* === 12. Glitch === */
@keyframes glitch-in {
0% { transform: translate(0); opacity: 0; }
20% { transform: translate(-5px, 5px); opacity: 1; }
40% { transform: translate(-5px, -5px); }
60% { transform: translate(5px, 5px); }
80% { transform: translate(5px, -5px); }
100% { transform: translate(0); opacity: 1; }
}
.animate-glitch-in { animation-name: glitch-in; }
/* === 13. Ken Burns Extreme === */
@keyframes ken-burns-extreme-in {
0% { transform: scale(1.5) translate(10%, 10%); opacity: 0; }
100% { transform: scale(1) translate(0, 0); opacity: 1; }
}
.animate-ken-burns-extreme-in { animation-name: ken-burns-extreme-in; }
/* === 14. Pixelate Simulation === */
@keyframes pixelate-sim-in {
0% { filter: blur(10px) contrast(200%); opacity: 0; }
50% { filter: blur(5px) contrast(150%); opacity: 0.5; }
100% { filter: blur(0) contrast(100%); opacity: 1; }
}
.animate-pixelate-sim-in { animation-name: pixelate-sim-in; }
/* === 15. Flash (Lens Flare Vibe) === */
@keyframes flash-in {
0% { filter: brightness(300%); opacity: 0; }
50% { filter: brightness(200%); opacity: 1; }
100% { filter: brightness(100%); opacity: 1; }
}
.animate-flash-in { animation-name: flash-in; }
`}</style>
</div>
);
};
export default SlideshowBackground;