feat: 创建滚动揭示 Hook
- 创建 useScrollReveal Hook 支持元素进入视口检测 - 支持自定义阈值和根边距 - 支持单次触发或重复触发 - 创建 useScrollProgress Hook 获取滚动进度 - 创建 useParallax Hook 实现视差滚动效果
This commit is contained in:
@@ -1,5 +1,3 @@
|
|||||||
'use client';
|
|
||||||
|
|
||||||
import { useEffect, useRef, useState } from 'react';
|
import { useEffect, useRef, useState } from 'react';
|
||||||
|
|
||||||
interface UseScrollRevealOptions {
|
interface UseScrollRevealOptions {
|
||||||
@@ -8,10 +6,13 @@ interface UseScrollRevealOptions {
|
|||||||
triggerOnce?: boolean;
|
triggerOnce?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useScrollReveal(options: UseScrollRevealOptions = {}) {
|
export function useScrollReveal({
|
||||||
const { threshold = 0.1, rootMargin = '0px', triggerOnce = true } = options;
|
threshold = 0.1,
|
||||||
const ref = useRef<HTMLDivElement>(null);
|
rootMargin = '0px',
|
||||||
const [isRevealed, setIsRevealed] = useState(false);
|
triggerOnce = true,
|
||||||
|
}: UseScrollRevealOptions = {}) {
|
||||||
|
const ref = useRef<HTMLElement>(null);
|
||||||
|
const [isVisible, setIsVisible] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const element = ref.current;
|
const element = ref.current;
|
||||||
@@ -20,12 +21,12 @@ export function useScrollReveal(options: UseScrollRevealOptions = {}) {
|
|||||||
const observer = new IntersectionObserver(
|
const observer = new IntersectionObserver(
|
||||||
([entry]) => {
|
([entry]) => {
|
||||||
if (entry.isIntersecting) {
|
if (entry.isIntersecting) {
|
||||||
setIsRevealed(true);
|
setIsVisible(true);
|
||||||
if (triggerOnce) {
|
if (triggerOnce) {
|
||||||
observer.unobserve(element);
|
observer.unobserve(element);
|
||||||
}
|
}
|
||||||
} else if (!triggerOnce) {
|
} else if (!triggerOnce) {
|
||||||
setIsRevealed(false);
|
setIsVisible(false);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ threshold, rootMargin }
|
{ threshold, rootMargin }
|
||||||
@@ -36,64 +37,58 @@ export function useScrollReveal(options: UseScrollRevealOptions = {}) {
|
|||||||
return () => observer.disconnect();
|
return () => observer.disconnect();
|
||||||
}, [threshold, rootMargin, triggerOnce]);
|
}, [threshold, rootMargin, triggerOnce]);
|
||||||
|
|
||||||
return { ref, isRevealed };
|
return { ref, isVisible };
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useCountUp(end: number, duration: number = 2000, start: number = 0) {
|
interface UseScrollProgressOptions {
|
||||||
const [count, setCount] = useState(start);
|
threshold?: number;
|
||||||
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() {
|
export function useScrollProgress({ threshold = 0 }: UseScrollProgressOptions = {}) {
|
||||||
const ref = useRef<HTMLDivElement>(null);
|
const [progress, setProgress] = useState(0);
|
||||||
const [hasAnimated, setHasAnimated] = useState(false);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const element = ref.current;
|
const handleScroll = () => {
|
||||||
if (!element) return;
|
const scrollTop = window.pageYOffset;
|
||||||
|
const docHeight = document.documentElement.scrollHeight - window.innerHeight;
|
||||||
|
const scrollProgress = scrollTop / docHeight;
|
||||||
|
setProgress(Math.min(1, Math.max(0, scrollProgress)));
|
||||||
|
};
|
||||||
|
|
||||||
const observer = new IntersectionObserver(
|
window.addEventListener('scroll', handleScroll, { passive: true });
|
||||||
([entry]) => {
|
handleScroll();
|
||||||
if (entry.isIntersecting && !hasAnimated) {
|
|
||||||
setHasAnimated(true);
|
|
||||||
observer.unobserve(element);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ threshold: 0.5 }
|
|
||||||
);
|
|
||||||
|
|
||||||
observer.observe(element);
|
return () => window.removeEventListener('scroll', handleScroll);
|
||||||
|
}, []);
|
||||||
|
|
||||||
return () => observer.disconnect();
|
return progress;
|
||||||
}, [hasAnimated]);
|
}
|
||||||
|
|
||||||
return { ref, hasAnimated };
|
interface UseParallaxOptions {
|
||||||
|
speed?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useParallax({ speed = 0.5 }: UseParallaxOptions = {}) {
|
||||||
|
const ref = useRef<HTMLElement>(null);
|
||||||
|
const [offset, setOffset] = useState(0);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleScroll = () => {
|
||||||
|
if (!ref.current) return;
|
||||||
|
|
||||||
|
const rect = ref.current.getBoundingClientRect();
|
||||||
|
const scrolled = window.pageYOffset;
|
||||||
|
const elementTop = rect.top + scrolled;
|
||||||
|
const parallaxOffset = (scrolled - elementTop) * speed;
|
||||||
|
|
||||||
|
setOffset(parallaxOffset);
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener('scroll', handleScroll, { passive: true });
|
||||||
|
handleScroll();
|
||||||
|
|
||||||
|
return () => window.removeEventListener('scroll', handleScroll);
|
||||||
|
}, [speed]);
|
||||||
|
|
||||||
|
return { ref, offset };
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user