aura-glass
Version:
A comprehensive glassmorphism design system for React applications with 142+ production-ready components
243 lines (240 loc) • 7.78 kB
JavaScript
import { useRef, useMemo, useCallback, useEffect } from 'react';
function usePhysicsAnimation(config = {}, enabled = true) {
const {
stiffness = 100,
damping = 10,
mass = 1,
velocity = 0,
friction = 0.98
} = config;
const animationRef = useRef();
const physicsStateRef = useRef(new Map());
const lastFrameRef = useRef(null);
const callbacksRef = useRef(new Map());
// Spring configuration
const springConfig = useMemo(() => ({
stiffness,
damping,
mass,
precision: 0.01
}), [stiffness, damping, mass]);
// Animation loop
const animate = useCallback(timestamp => {
if (!lastFrameRef.current) {
lastFrameRef.current = {
id: 0,
timestamp,
deltaTime: 0
};
animationRef.current = requestAnimationFrame(animate);
return;
}
const deltaTime = (timestamp - lastFrameRef.current.timestamp) / 1000; // Convert to seconds
lastFrameRef.current = {
id: lastFrameRef.current.id + 1,
timestamp,
deltaTime
};
let hasActiveAnimations = false;
// Update all physics states
physicsStateRef.current.forEach((state, key) => {
if (!state.isAnimating) return;
// Calculate spring force
const displacement = state.target - state.position;
const springForce = displacement * springConfig.stiffness;
const dampingForce = -state.velocity * springConfig.damping;
// Update acceleration (F = ma)
state.acceleration = (springForce + dampingForce) / springConfig.mass;
// Update velocity (v = v + a * dt)
state.velocity += state.acceleration * deltaTime;
// Apply friction
state.velocity *= friction;
// Update position (p = p + v * dt)
state.position += state.velocity * deltaTime;
// Check if animation is complete
const isAtRest = Math.abs(state.velocity) < springConfig.precision && Math.abs(displacement) < springConfig.precision;
if (isAtRest) {
state.position = state.target;
state.velocity = 0;
state.acceleration = 0;
state.isAnimating = false;
} else {
hasActiveAnimations = true;
}
// Call update callback
const callback = callbacksRef.current.get(key);
if (callback) {
callback(state.position);
}
});
if (hasActiveAnimations && enabled) {
animationRef.current = requestAnimationFrame(animate);
} else {
animationRef.current = undefined;
}
}, [springConfig, friction, enabled]);
// Start animation for a specific key
const startAnimation = useCallback((key, from, to, callback) => {
if (!enabled) {
callback?.(to);
return;
}
const existingState = physicsStateRef.current.get(key);
physicsStateRef.current.set(key, {
position: existingState?.position ?? from,
velocity: existingState?.velocity ?? velocity,
acceleration: 0,
target: to,
isAnimating: true
});
if (callback) {
callbacksRef.current.set(key, callback);
}
// Start animation loop if not already running
if (!animationRef.current) {
animationRef.current = requestAnimationFrame(animate);
}
}, [enabled, velocity, animate]);
// Stop animation for a specific key
const stopAnimation = useCallback(key => {
const state = physicsStateRef.current.get(key);
if (state) {
state.isAnimating = false;
state.velocity = 0;
state.acceleration = 0;
}
}, []);
// Update target value for a specific key
const updateTarget = useCallback((key, target) => {
const state = physicsStateRef.current.get(key);
if (state) {
state.target = target;
state.isAnimating = true;
// Restart animation if not running
if (!animationRef.current) {
animationRef.current = requestAnimationFrame(animate);
}
}
}, [animate]);
// Get current value for a specific key
const getCurrentValue = useCallback(key => {
const state = physicsStateRef.current.get(key);
return state?.position ?? 0;
}, []);
// Check if animation is active for a specific key
const isAnimating = useCallback(key => {
const state = physicsStateRef.current.get(key);
return state?.isAnimating ?? false;
}, []);
// Clean up on unmount
useEffect(() => {
return () => {
if (animationRef.current) {
cancelAnimationFrame(animationRef.current);
}
};
}, []);
return {
startAnimation,
stopAnimation,
updateTarget,
getCurrentValue,
isAnimating,
springConfig,
// Additional properties for compatibility with expected interface
value: 0,
// This would need to be implemented based on the specific use case
applyPopIn: () => {
// Pop-in animation implementation
startAnimation('popIn', 0, 1);
if (process.env.NODE_ENV === 'development') {
console.log('Pop-in animation triggered');
}
}
};
}
// Hook for animating chart data points
function useDataPointAnimation(dataPoints, physicsConfig) {
const physics = usePhysicsAnimation(physicsConfig);
const animateDataPoints = useCallback((targetPoints, duration = 1000, onUpdate) => {
if (!(targetPoints?.length || 0)) return;
const animatedPoints = [...targetPoints];
targetPoints.forEach((point, index) => {
const key = `point-${index}`;
const targetY = typeof point.y === 'number' ? point.y : 0;
physics.startAnimation(key, 0,
// Start from 0
targetY, value => {
if (animatedPoints) {
animatedPoints[index] = {
...point,
y: value
};
}
onUpdate?.(animatedPoints);
});
});
}, [physics]);
return {
animateDataPoints,
...physics
};
}
// Hook for animating chart series
function useSeriesAnimation(series, physicsConfig) {
const physics = usePhysicsAnimation(physicsConfig);
const animateSeries = useCallback((targetSeries, staggerDelay = 100, onUpdate) => {
if (!(targetSeries?.length || 0)) return;
const animatedSeries = [...targetSeries];
targetSeries.forEach((seriesItem, seriesIndex) => {
const delay = seriesIndex * staggerDelay;
seriesItem.data?.forEach((point, pointIndex) => {
const key = `series-${seriesIndex}-point-${pointIndex}`;
const targetY = typeof point.y === 'number' ? point.y : 0;
setTimeout(() => {
physics.startAnimation(key, 0, targetY, value => {
if (animatedSeries[seriesIndex].data) {
animatedSeries[seriesIndex].data[pointIndex] = {
...point,
y: value
};
}
onUpdate?.(animatedSeries);
});
}, delay);
});
});
}, [physics]);
return {
animateSeries,
...physics
};
}
// Hook for smooth transitions between chart states
function useChartTransition(physicsConfig) {
const physics = usePhysicsAnimation(physicsConfig);
const transitionTo = useCallback((fromState, toState, onTransition) => {
const keys = Object.keys(toState);
keys.forEach(key => {
const fromValue = fromState?.[key] || 0;
const toValue = toState?.[key] || 0;
physics.startAnimation(`transition-${key}`, fromValue, toValue, value => {
const currentState = {
...fromState
};
keys.forEach(k => {
if (currentState) {
currentState[k] = physics.getCurrentValue(`transition-${k}`);
}
});
onTransition?.(currentState);
});
});
}, [physics]);
return {
transitionTo,
...physics
};
}
export { useChartTransition, useDataPointAnimation, usePhysicsAnimation, useSeriesAnimation };
//# sourceMappingURL=usePhysicsAnimation.js.map