打磨细节

This commit is contained in:
2026-01-28 17:55:01 +08:00
parent 08af95116e
commit 0a1bd07492
36 changed files with 1649 additions and 466 deletions

View File

@@ -2,14 +2,15 @@
import { useState, useEffect, useRef } from 'react';
import Image from 'next/image';
import { useLanguage } from '@/contexts/LanguageContext';
// 数字增长动画Hook
// 数字增长动画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<HTMLDivElement>(null);
const startValueRef = useRef<number>(end);
const timerRef = useRef<NodeJS.Timeout | null>(null);
// 客户端挂载后设置随机起始值
useEffect(() => {
@@ -23,28 +24,38 @@ function useCountUp(end: number, duration: number = 1500, startRangePercent: num
const observer = new IntersectionObserver(
(entries) => {
if (entries[0].isIntersecting && !hasAnimated) {
setHasAnimated(true);
const start = startValueRef.current;
const startTime = Date.now();
if (entries[0].isIntersecting) {
// 进入视口 - 检查是否需要开始动画
if (!timerRef.current) {
const start = startValueRef.current;
const startTime = Date.now();
const timer = setInterval(() => {
const elapsed = Date.now() - startTime;
const progress = Math.min(elapsed / duration, 1);
timerRef.current = 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);
// 使用缓动函数 - easeOutCubic 更自然
const easeOutCubic = 1 - Math.pow(1 - progress, 3);
const currentCount = Math.floor(start + (end - start) * easeOutCubic);
setCount(currentCount);
setCount(currentCount);
if (progress === 1) {
setCount(end); // 确保最终值准确
clearInterval(timer);
}
}, 16);
return () => clearInterval(timer);
if (progress === 1) {
setCount(end); // 确保最终值准确
if (timerRef.current) {
clearInterval(timerRef.current);
timerRef.current = null;
}
}
}, 16);
}
} else {
// 离开视口 - 停止并重置
if (timerRef.current) {
clearInterval(timerRef.current);
timerRef.current = null;
}
setCount(startValueRef.current);
}
},
{ threshold: 0.1 }
@@ -54,8 +65,14 @@ function useCountUp(end: number, duration: number = 1500, startRangePercent: num
observer.observe(elementRef.current);
}
return () => observer.disconnect();
}, [end, duration, hasAnimated, mounted]);
return () => {
observer.disconnect();
if (timerRef.current) {
clearInterval(timerRef.current);
timerRef.current = null;
}
};
}, [end, duration, mounted]);
return { count, elementRef };
}
@@ -75,6 +92,7 @@ function formatNumber(num: number, prefix: string = '', suffix: string = '', for
}
export default function StatsSection() {
const { t } = useLanguage();
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%开始
@@ -135,7 +153,7 @@ export default function StatsSection() {
fontWeight: 400
}}
>
Total Value Locked
{t('stats.tvl')}
</div>
<div
ref={tvl.elementRef}
@@ -173,7 +191,7 @@ export default function StatsSection() {
fontWeight: 400
}}
>
Avg. APY
{t('stats.apy')}
</div>
<div
ref={apy.elementRef}
@@ -211,7 +229,7 @@ export default function StatsSection() {
fontWeight: 400
}}
>
Yield Captured
{t('stats.yield')}
</div>
<div
ref={yield_.elementRef}
@@ -249,7 +267,7 @@ export default function StatsSection() {
fontWeight: 400
}}
>
Connected Users
{t('stats.users')}
</div>
{/* .frame-38 - Number and avatars */}