222 lines
7.7 KiB
TypeScript
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; |