打磨细节
This commit is contained in:
103
components/ShatterTransition.tsx
Normal file
103
components/ShatterTransition.tsx
Normal file
@@ -0,0 +1,103 @@
|
||||
'use client';
|
||||
|
||||
import { useEffect, useState, useMemo } from 'react';
|
||||
|
||||
interface ShatterTransitionProps {
|
||||
isActive: boolean;
|
||||
onComplete: () => void;
|
||||
}
|
||||
|
||||
// 增加网格密度以获得更细腻的破碎感
|
||||
const GRID_COLS = 10;
|
||||
const GRID_ROWS = 10;
|
||||
|
||||
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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user