210 lines
4.2 KiB
TypeScript
210 lines
4.2 KiB
TypeScript
import React, { useEffect, useState } from 'react';
|
|
|
|
interface PageTransitionProps {
|
|
children: React.ReactNode;
|
|
className?: string;
|
|
delay?: number;
|
|
}
|
|
|
|
/**
|
|
* 页面过渡动画组件
|
|
* 提供平滑的页面切换效果
|
|
*/
|
|
export const PageTransition: React.FC<PageTransitionProps> = ({
|
|
children,
|
|
className = '',
|
|
delay = 0
|
|
}) => {
|
|
const [isVisible, setIsVisible] = useState(false);
|
|
|
|
useEffect(() => {
|
|
const timer = setTimeout(() => {
|
|
setIsVisible(true);
|
|
}, delay);
|
|
|
|
return () => clearTimeout(timer);
|
|
}, [delay]);
|
|
|
|
return (
|
|
<div
|
|
className={`
|
|
transition-all duration-500 ease-out
|
|
${isVisible
|
|
? 'opacity-100 translate-y-0'
|
|
: 'opacity-0 translate-y-4'
|
|
}
|
|
${className}
|
|
`}
|
|
>
|
|
{children}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
/**
|
|
* 交错动画容器
|
|
* 为子元素提供依次出现的动画效果
|
|
*/
|
|
export const StaggeredContainer: React.FC<{
|
|
children: React.ReactNode;
|
|
staggerDelay?: number;
|
|
className?: string;
|
|
}> = ({ children, staggerDelay = 100, className = '' }) => {
|
|
return (
|
|
<div className={className}>
|
|
{React.Children.map(children, (child, index) => (
|
|
<PageTransition delay={index * staggerDelay}>
|
|
{child}
|
|
</PageTransition>
|
|
))}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
/**
|
|
* 滑入动画组件
|
|
*/
|
|
export const SlideIn: React.FC<{
|
|
children: React.ReactNode;
|
|
direction?: 'left' | 'right' | 'up' | 'down';
|
|
delay?: number;
|
|
className?: string;
|
|
}> = ({ children, direction = 'up', delay = 0, className = '' }) => {
|
|
const [isVisible, setIsVisible] = useState(false);
|
|
|
|
useEffect(() => {
|
|
const timer = setTimeout(() => {
|
|
setIsVisible(true);
|
|
}, delay);
|
|
|
|
return () => clearTimeout(timer);
|
|
}, [delay]);
|
|
|
|
const getTransformClasses = () => {
|
|
const transforms = {
|
|
left: isVisible ? 'translate-x-0' : '-translate-x-8',
|
|
right: isVisible ? 'translate-x-0' : 'translate-x-8',
|
|
up: isVisible ? 'translate-y-0' : 'translate-y-8',
|
|
down: isVisible ? 'translate-y-0' : '-translate-y-8'
|
|
};
|
|
return transforms[direction];
|
|
};
|
|
|
|
return (
|
|
<div
|
|
className={`
|
|
transition-all duration-500 ease-out
|
|
${isVisible ? 'opacity-100' : 'opacity-0'}
|
|
${getTransformClasses()}
|
|
${className}
|
|
`}
|
|
>
|
|
{children}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
/**
|
|
* 缩放动画组件
|
|
*/
|
|
export const ScaleIn: React.FC<{
|
|
children: React.ReactNode;
|
|
delay?: number;
|
|
className?: string;
|
|
}> = ({ children, delay = 0, className = '' }) => {
|
|
const [isVisible, setIsVisible] = useState(false);
|
|
|
|
useEffect(() => {
|
|
const timer = setTimeout(() => {
|
|
setIsVisible(true);
|
|
}, delay);
|
|
|
|
return () => clearTimeout(timer);
|
|
}, [delay]);
|
|
|
|
return (
|
|
<div
|
|
className={`
|
|
transition-all duration-300 ease-out
|
|
${isVisible
|
|
? 'opacity-100 scale-100'
|
|
: 'opacity-0 scale-95'
|
|
}
|
|
${className}
|
|
`}
|
|
>
|
|
{children}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
/**
|
|
* 悬停放大组件
|
|
*/
|
|
export const HoverScale: React.FC<{
|
|
children: React.ReactNode;
|
|
scale?: number;
|
|
className?: string;
|
|
}> = ({ children, scale = 1.05, className = '' }) => {
|
|
return (
|
|
<div
|
|
className={`
|
|
transition-transform duration-200 ease-out
|
|
hover:scale-[${scale}]
|
|
${className}
|
|
`}
|
|
>
|
|
{children}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
/**
|
|
* 悬停倾斜组件
|
|
*/
|
|
export const HoverTilt: React.FC<{
|
|
children: React.ReactNode;
|
|
degree?: number;
|
|
className?: string;
|
|
}> = ({ children, degree = 2, className = '' }) => {
|
|
return (
|
|
<div
|
|
className={`
|
|
transition-transform duration-200 ease-out
|
|
hover:rotate-[${degree}deg]
|
|
${className}
|
|
`}
|
|
>
|
|
{children}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
/**
|
|
* 脉冲动画组件
|
|
*/
|
|
export const Pulse: React.FC<{
|
|
children: React.ReactNode;
|
|
className?: string;
|
|
}> = ({ children, className = '' }) => {
|
|
return (
|
|
<div className={`animate-pulse ${className}`}>
|
|
{children}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
/**
|
|
* 呼吸动画组件
|
|
*/
|
|
export const Breathe: React.FC<{
|
|
children: React.ReactNode;
|
|
className?: string;
|
|
}> = ({ children, className = '' }) => {
|
|
return (
|
|
<div className={`animate-breathe ${className}`}>
|
|
{children}
|
|
</div>
|
|
);
|
|
};
|