Files
asset-homepage/components/ShatterTransition.tsx

104 lines
3.5 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use client';
import { useEffect, useState, useMemo } from 'react';
interface ShatterTransitionProps {
isActive: boolean;
onComplete: () => void;
}
// 优化性能:减少碎片数量
const GRID_COLS = 7;
const GRID_ROWS = 7;
export default function ShatterTransition({ isActive, onComplete }: ShatterTransitionProps) {
const [isAnimating, setIsAnimating] = useState(false);
useEffect(() => {
if (isActive) {
setIsAnimating(true);
// 总动画时间 = 最大延迟 + 单个动画时长
// 这里预留充足时间确保最后一片落完
const timer = setTimeout(() => {
setIsAnimating(false);
onComplete();
}, 1500);
return () => clearTimeout(timer);
} else {
setIsAnimating(false);
}
}, [isActive, onComplete]);
// 预先计算碎片数据,避免重渲染时的性能抖动
const fragments = useMemo(() => {
return Array.from({ length: GRID_COLS * GRID_ROWS }, (_, i) => {
const col = i % GRID_COLS;
const row = Math.floor(i / GRID_COLS);
// 核心算法:计算距离左上角的距离 (欧几里得距离)
// 距离越远delay 越大
const distance = Math.sqrt(col * col + row * row);
const maxDistance = Math.sqrt(GRID_COLS * GRID_COLS + GRID_ROWS * GRID_ROWS);
const normalizedDistance = distance / maxDistance;
// 爆炸参数
const randomX = (Math.random() - 0.5) * 200; // X轴偏移
const randomY = Math.random() * 300 + 100; // Y轴主要向下掉落
const randomRotateX = (Math.random() - 0.5) * 720; // 剧烈翻滚
const randomRotateY = (Math.random() - 0.5) * 720;
const randomScale = 0.5 + Math.random() * 0.5; // 碎片大小不一
return {
id: i,
col,
row,
// 延迟时间:基准延迟 + 随机微扰 (让波浪不那么死板)
delay: normalizedDistance * 600 + (Math.random() * 100),
randomX,
randomY,
randomRotateX,
randomRotateY,
randomScale
};
});
}, []);
if (!isActive && !isAnimating) return null;
return (
<div
className="fixed inset-0 z-[9999] pointer-events-none overflow-hidden"
style={{ perspective: '1200px' }} // 开启3D透视这很重要
>
<div className="relative w-full h-full bg-transparent">
{fragments.map((frag) => (
<div
key={frag.id}
className="absolute shatter-fragment will-change-transform"
style={{
left: `${(frag.col / GRID_COLS) * 100}%`,
top: `${(frag.row / GRID_ROWS) * 100}%`,
width: `${100 / GRID_COLS + 0.1}%`, // +0.1% 防止渲染缝隙
height: `${100 / GRID_ROWS + 0.1}%`,
// 动画变量传入 CSS
'--delay': `${frag.delay}ms`,
'--tx': `${frag.randomX}px`,
'--ty': `${frag.randomY}px`,
'--rx': `${frag.randomRotateX}deg`,
'--ry': `${frag.randomRotateY}deg`,
'--s': `${frag.randomScale}`,
} as React.CSSProperties}
>
{/* 内部容器负责材质,外部容器负责位置,分离关注点 */}
<div className="w-full h-full glass-shard bg-white backdrop-blur-lg border-[0.5px] border-white/30 shadow-md" />
</div>
))}
</div>
{/* 可选:背景遮罩,防止碎片飞走后直接漏出底部内容,根据需求调整 */}
<div className="absolute inset-0 bg-white -z-10 animate-fade-out" />
</div>
);
}