'use client'; import { useState, useEffect, useRef } from 'react'; import Image from 'next/image'; // 数字增长动画Hook function useCountUp(end: number, duration: number = 1500, startRangePercent: number = 0.75) { const [mounted, setMounted] = useState(false); const [count, setCount] = useState(end); // 初始值设为目标值,避免hydration错误 const [hasAnimated, setHasAnimated] = useState(false); const elementRef = useRef(null); const startValueRef = useRef(end); // 客户端挂载后设置随机起始值 useEffect(() => { setMounted(true); startValueRef.current = Math.floor(end * (startRangePercent + Math.random() * 0.15)); setCount(startValueRef.current); }, [end, startRangePercent]); useEffect(() => { if (!mounted) return; const observer = new IntersectionObserver( (entries) => { if (entries[0].isIntersecting && !hasAnimated) { setHasAnimated(true); const start = startValueRef.current; const startTime = Date.now(); const timer = setInterval(() => { const elapsed = Date.now() - startTime; const progress = Math.min(elapsed / duration, 1); // 使用缓动函数 - easeOutCubic 更自然 const easeOutCubic = 1 - Math.pow(1 - progress, 3); const currentCount = Math.floor(start + (end - start) * easeOutCubic); setCount(currentCount); if (progress === 1) { setCount(end); // 确保最终值准确 clearInterval(timer); } }, 16); return () => clearInterval(timer); } }, { threshold: 0.1 } ); if (elementRef.current) { observer.observe(elementRef.current); } return () => observer.disconnect(); }, [end, duration, hasAnimated, mounted]); return { count, elementRef }; } // 格式化数字 - 确保始终显示正确的单位 function formatNumber(num: number, prefix: string = '', suffix: string = '', forceUnit: 'M' | 'K' | '' = '') { if (forceUnit === 'M') { return `${prefix}${Math.floor(num)}${suffix}`; } else if (forceUnit === 'K') { return `${prefix}${Math.floor(num)}${suffix}`; } else if (num >= 1000000) { return `${prefix}${Math.floor(num / 1000000)}M${suffix}`; } else if (num >= 1000) { return `${prefix}${Math.floor(num / 1000)}K${suffix}`; } return `${prefix}${num.toLocaleString()}${suffix}`; } export default function StatsSection() { const tvl = useCountUp(485, 1500, 0.80); // 从80-95%开始 const apy = useCountUp(515, 1500, 0.85); // 5.15 * 100,从85-100%开始 const yield_ = useCountUp(45, 1500, 0.75); // 从75-90%开始 const users = useCountUp(23928, 1800, 0.80); // 从80-95%开始 const [mounted, setMounted] = useState(false); const [imagesVisible, setImagesVisible] = useState(false); useEffect(() => { // 客户端挂载 setMounted(true); // 延迟显示图片动画 const timer = setTimeout(() => { setImagesVisible(true); }, 500); return () => clearTimeout(timer); }, []); return (
{/* .section2 */}
{/* .container4 - Grid container */}
{/* Card 1 - Total Value Locked */}
Total Value Locked
{formatNumber(tvl.count, '$', 'M+', 'M')}
{/* Card 2 - Avg. APY */}
Avg. APY
{(apy.count / 100).toFixed(2)}%
{/* Card 3 - Yield Captured */}
Yield Captured
{formatNumber(yield_.count, '$', 'M', 'M')}
{/* Card 4 - Connected Users */}
Connected Users
{/* .frame-38 - Number and avatars */}
{/* Number */}
{users.count.toLocaleString()}+
{/* .container9 - Avatar group */}
{/* Avatar 1 - image-23 */}
User
{/* Avatar 2 - image-24 */}
User
{/* Avatar 3 - image-25 */}
User
{/* Avatar 4 - image-252 */}
User
); }