feat: add mobile optimization with hooks and touch components

This commit is contained in:
张翔
2026-02-13 14:46:21 +08:00
parent e829451fa3
commit 7914decdaf
7 changed files with 396 additions and 0 deletions
+99
View File
@@ -0,0 +1,99 @@
'use client';
import { useEffect, useRef, useState } from 'react';
interface UseScrollRevealOptions {
threshold?: number;
rootMargin?: string;
triggerOnce?: boolean;
}
export function useScrollReveal(options: UseScrollRevealOptions = {}) {
const { threshold = 0.1, rootMargin = '0px', triggerOnce = true } = options;
const ref = useRef<HTMLDivElement>(null);
const [isRevealed, setIsRevealed] = useState(false);
useEffect(() => {
const element = ref.current;
if (!element) return;
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) {
setIsRevealed(true);
if (triggerOnce) {
observer.unobserve(element);
}
} else if (!triggerOnce) {
setIsRevealed(false);
}
},
{ threshold, rootMargin }
);
observer.observe(element);
return () => observer.disconnect();
}, [threshold, rootMargin, triggerOnce]);
return { ref, isRevealed };
}
export function useCountUp(end: number, duration: number = 2000, start: number = 0) {
const [count, setCount] = useState(start);
const [isAnimating, setIsAnimating] = useState(false);
const startAnimation = () => {
if (isAnimating) return;
setIsAnimating(true);
const startTime = Date.now();
const diff = end - start;
const animate = () => {
const elapsed = Date.now() - startTime;
const progress = Math.min(elapsed / duration, 1);
const easeOutQuart = 1 - Math.pow(1 - progress, 4);
const current = Math.floor(start + diff * easeOutQuart);
setCount(current);
if (progress < 1) {
requestAnimationFrame(animate);
} else {
setIsAnimating(false);
}
};
requestAnimationFrame(animate);
};
return { count, startAnimation, isAnimating };
}
export function useStampAnimation() {
const ref = useRef<HTMLDivElement>(null);
const [hasAnimated, setHasAnimated] = useState(false);
useEffect(() => {
const element = ref.current;
if (!element) return;
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting && !hasAnimated) {
setHasAnimated(true);
observer.unobserve(element);
}
},
{ threshold: 0.5 }
);
observer.observe(element);
return () => observer.disconnect();
}, [hasAnimated]);
return { ref, hasAnimated };
}