UNPKG

aura-glass

Version:

A comprehensive glassmorphism design system for React applications with 142+ production-ready components

187 lines (184 loc) 5.59 kB
import { useState, useRef, useEffect, useCallback } from 'react'; const DEFAULT_CONFIG = { stiffness: 170, damping: 26, mass: 1, precision: 0.01, velocity: 0 }; function useMultiSpring(initialValues, options = {}) { const { config = DEFAULT_CONFIG, immediate = false, onRest, onFrame } = options; const [values, setValues] = useState(initialValues); const springStateRef = useRef({}); const animationRef = useRef(); const lastTimeRef = useRef(0); // Initialize spring state useEffect(() => { Object.keys(initialValues).forEach(key => { if (!springStateRef.current[key]) { springStateRef.current[key] = { current: initialValues[key], target: initialValues[key], velocity: config.velocity || 0, isAnimating: false }; } }); }, [initialValues, config.velocity]); // Physics simulation const simulate = useCallback(deltaTime => { let hasAnimatingValues = false; const newValues = {}; Object.keys(springStateRef.current).forEach(key => { const spring = springStateRef.current[key]; if (!spring.isAnimating) { newValues[key] = spring.current; return; } // Spring physics calculation const displacement = spring.target - spring.current; const springForce = displacement * config.stiffness; const dampingForce = spring.velocity * config.damping; const acceleration = (springForce - dampingForce) / config.mass; // Update velocity and position spring.velocity += acceleration * deltaTime; spring.current += spring.velocity * deltaTime; // Check if animation should stop const isAtRest = Math.abs(displacement) < (config.precision || 0.01) && Math.abs(spring.velocity) < (config.precision || 0.01); if (isAtRest) { spring.current = spring.target; spring.velocity = 0; spring.isAnimating = false; onRest?.(key); } else { hasAnimatingValues = true; } newValues[key] = spring.current; }); setValues(newValues); onFrame?.(newValues); return hasAnimatingValues; }, [config, onRest, onFrame]); // Animation loop const animate = useCallback(timestamp => { if (!lastTimeRef.current) { lastTimeRef.current = timestamp; } const deltaTime = Math.min((timestamp - lastTimeRef.current) / 1000, 0.016); // Cap at 60fps lastTimeRef.current = timestamp; const hasAnimatingValues = simulate(deltaTime); if (hasAnimatingValues) { animationRef.current = requestAnimationFrame(animate); } else { animationRef.current = undefined; lastTimeRef.current = 0; } }, [simulate]); // Start animation to new targets const start = useCallback(targets => { Object.keys(targets).forEach(key => { const targetValue = targets[key]; if (!springStateRef.current[key]) { springStateRef.current[key] = { current: targetValue, target: targetValue, velocity: 0, isAnimating: false }; } const spring = springStateRef.current[key]; if (immediate) { spring.current = targetValue; spring.target = targetValue; spring.velocity = 0; spring.isAnimating = false; } else { spring.target = targetValue; spring.isAnimating = true; } }); // Update current values immediately if immediate mode if (immediate) { setValues(targets); onFrame?.(targets); } else { // Start animation if not already running if (!animationRef.current) { animationRef.current = requestAnimationFrame(animate); } } }, [immediate, animate, onFrame]); // Stop all animations const stop = useCallback(() => { if (animationRef.current) { cancelAnimationFrame(animationRef.current); animationRef.current = undefined; } Object.keys(springStateRef.current).forEach(key => { const spring = springStateRef.current[key]; spring.target = spring.current; spring.velocity = 0; spring.isAnimating = false; }); lastTimeRef.current = 0; }, []); // Set immediate value without animation const set = useCallback(newValues => { Object.keys(newValues).forEach(key => { if (!springStateRef.current[key]) { springStateRef.current[key] = { current: newValues[key], target: newValues[key], velocity: 0, isAnimating: false }; } else { const spring = springStateRef.current[key]; spring.current = newValues[key]; spring.target = newValues[key]; spring.velocity = 0; spring.isAnimating = false; } }); setValues(prev => ({ ...prev, ...newValues })); onFrame?.({ ...values, ...newValues }); }, [values, onFrame]); // Get current velocity for a key const getVelocity = useCallback(key => { return springStateRef.current[key]?.velocity || 0; }, []); // Check if any values are animating const isAnimating = useCallback(() => { return Object.values(springStateRef.current).some(spring => spring.isAnimating); }, []); // Cleanup on unmount useEffect(() => { return () => { if (animationRef.current) { cancelAnimationFrame(animationRef.current); } }; }, []); return { values, start, stop, set, getVelocity, isAnimating, config }; } export { useMultiSpring }; //# sourceMappingURL=useMultiSpringPhysics.js.map