打磨细节
This commit is contained in:
@@ -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 */}
|
||||
|
||||
Reference in New Issue
Block a user