- Introduced DrawLine component for drawing animations. - Added LazyRevealImage component for lazy loading images with reveal animations. - Implemented TextDiffuseReveal for animated text reveals with staggered effects. - Created TopProgressBar for a dynamic loading indicator. - Added PointCloud mock for testing animations. - Developed useReducedMotion hook to handle user preferences for reduced motion. - Centralized animation configurations in config.ts for easier management. - Defined animation variants in variants.ts for consistent animation effects. - Established AnimationProvider to supply global animation settings. - Created tests for AnimationProvider and useReducedMotion hook. - Added SVG assets for branding and UI elements. - Documented frontend review plan and hero circuit board animation plan.
64 lines
1.4 KiB
TypeScript
64 lines
1.4 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect, useRef, useState } from "react";
|
|
import { useInView, useReducedMotion } from "framer-motion";
|
|
|
|
interface CounterValueProps {
|
|
value: number;
|
|
className?: string;
|
|
duration?: number;
|
|
prefix?: string;
|
|
suffix?: string;
|
|
once?: boolean;
|
|
amount?: number;
|
|
}
|
|
|
|
export function CounterValue({
|
|
value,
|
|
className = "",
|
|
duration = 1200,
|
|
prefix = "",
|
|
suffix = "",
|
|
once = true,
|
|
amount = 0.45,
|
|
}: Readonly<CounterValueProps>) {
|
|
const shouldReduceMotion = useReducedMotion();
|
|
const ref = useRef<HTMLSpanElement | null>(null);
|
|
const isInView = useInView(ref, { once, amount });
|
|
const [displayValue, setDisplayValue] = useState(0);
|
|
|
|
useEffect(() => {
|
|
if (!isInView) return;
|
|
|
|
if (shouldReduceMotion) {
|
|
setDisplayValue(value);
|
|
return;
|
|
}
|
|
|
|
const start = performance.now();
|
|
let rafId = 0;
|
|
|
|
const frame = (now: number) => {
|
|
const elapsed = now - start;
|
|
const progress = Math.min(elapsed / duration, 1);
|
|
const eased = 1 - Math.pow(1 - progress, 3);
|
|
setDisplayValue(Math.round(value * eased));
|
|
|
|
if (progress < 1) {
|
|
rafId = requestAnimationFrame(frame);
|
|
}
|
|
};
|
|
|
|
rafId = requestAnimationFrame(frame);
|
|
return () => cancelAnimationFrame(rafId);
|
|
}, [isInView, value, duration, shouldReduceMotion]);
|
|
|
|
return (
|
|
<span ref={ref} className={className}>
|
|
{prefix}
|
|
{displayValue}
|
|
{suffix}
|
|
</span>
|
|
);
|
|
}
|