UNPKG

aura-glass

Version:

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

231 lines (228 loc) 6.51 kB
'use client'; import { useState, useRef, useCallback } from 'react'; /** * Hook for 3D transforms */ const use3DTransform = (options = {}) => { const { initialTransform = {}, perspective = 1000, transformOrigin = 'center center', backfaceVisibility = 'visible', transformStyle = 'preserve-3d' } = options; const defaultTransform = { rotateX: 0, rotateY: 0, rotateZ: 0, translateX: 0, translateY: 0, translateZ: 0, scale: 1, scaleX: 1, scaleY: 1, scaleZ: 1, perspective: perspective, ...initialTransform }; const [transform, setTransform] = useState(defaultTransform); const animationFrameRef = useRef(null); /** * Generate CSS transform string */ const getTransformString = useCallback((state = transform) => { return [`perspective(${state.perspective}px)`, `rotateX(${state.rotateX}deg)`, `rotateY(${state.rotateY}deg)`, `rotateZ(${state.rotateZ}deg)`, `translateX(${state.translateX}px)`, `translateY(${state.translateY}px)`, `translateZ(${state.translateZ}px)`, `scale3d(${state.scaleX}, ${state.scaleY}, ${state.scaleZ})`].join(' '); }, [transform]); /** * Get CSS style object */ const getStyle = useCallback(() => { return { transform: getTransformString(), transformOrigin, backfaceVisibility, transformStyle }; }, [getTransformString, transformOrigin, backfaceVisibility, transformStyle]); /** * Update transform */ const updateTransform = useCallback(updates => { setTransform(prev => ({ ...prev, ...updates })); }, []); /** * Set rotation */ const setRotation = useCallback((x, y, z = 0) => { updateTransform({ rotateX: x, rotateY: y, rotateZ: z }); }, [updateTransform]); /** * Set translation */ const setTranslation = useCallback((x, y, z = 0) => { updateTransform({ translateX: x, translateY: y, translateZ: z }); }, [updateTransform]); /** * Set scale */ const setScale = useCallback(scale => { if (typeof scale === 'number') { updateTransform({ scale, scaleX: scale, scaleY: scale, scaleZ: scale }); } else { updateTransform({ scaleX: scale.x, scaleY: scale.y, scaleZ: scale.z ?? 1 }); } }, [updateTransform]); /** * Rotate from mouse/touch position */ const rotateFromPointer = useCallback((x, y, containerWidth, containerHeight, maxRotation = 20) => { const centerX = containerWidth / 2; const centerY = containerHeight / 2; const rotateY = (x - centerX) / centerX * maxRotation; const rotateX = -((y - centerY) / centerY) * maxRotation; setRotation(rotateX, rotateY, transform.rotateZ); }, [setRotation, transform.rotateZ]); /** * Animated rotation */ const animateRotation = useCallback((targetX, targetY, targetZ = 0, duration = 300) => { const startTime = Date.now(); const startX = transform.rotateX; const startY = transform.rotateY; const startZ = transform.rotateZ; const animate = () => { const elapsed = Date.now() - startTime; const progress = Math.min(elapsed / duration, 1); // Easing function (ease-out) const eased = 1 - Math.pow(1 - progress, 3); const currentX = startX + (targetX - startX) * eased; const currentY = startY + (targetY - startY) * eased; const currentZ = startZ + (targetZ - startZ) * eased; setRotation(currentX, currentY, currentZ); if (progress < 1) { animationFrameRef.current = requestAnimationFrame(animate); } }; if (animationFrameRef.current) { cancelAnimationFrame(animationFrameRef.current); } animationFrameRef.current = requestAnimationFrame(animate); }, [transform, setRotation]); /** * Reset transform */ const reset = useCallback((animated = true) => { if (animated) { animateRotation(0, 0, 0); // Animate other properties too const startTime = Date.now(); const duration = 300; const animate = () => { const elapsed = Date.now() - startTime; const progress = Math.min(elapsed / duration, 1); const eased = 1 - Math.pow(1 - progress, 3); updateTransform({ translateX: transform.translateX * (1 - eased), translateY: transform.translateY * (1 - eased), translateZ: transform.translateZ * (1 - eased), scale: transform.scale + (1 - transform.scale) * eased, scaleX: transform.scaleX + (1 - transform.scaleX) * eased, scaleY: transform.scaleY + (1 - transform.scaleY) * eased, scaleZ: transform.scaleZ + (1 - transform.scaleZ) * eased }); if (progress < 1) { requestAnimationFrame(animate); } }; animate(); } else { setTransform(defaultTransform); } }, [animateRotation, updateTransform, transform, defaultTransform]); /** * Apply card flip effect */ const flip = useCallback((duration = 600) => { const targetY = transform.rotateY >= 180 ? 0 : 180; animateRotation(transform.rotateX, targetY, transform.rotateZ, duration); }, [transform, animateRotation]); /** * Apply wobble effect */ const wobble = useCallback((intensity = 10, duration = 500) => { const steps = 4; const stepDuration = duration / steps; const wobbleSequence = [{ x: intensity, y: -intensity }, { x: -intensity, y: intensity }, { x: intensity, y: -intensity }, { x: 0, y: 0 }]; let currentStep = 0; const performStep = () => { if (currentStep < wobbleSequence.length) { const { x, y } = wobbleSequence[currentStep]; animateRotation(x, y, 0, stepDuration); currentStep++; setTimeout(performStep, stepDuration); } }; performStep(); }, [animateRotation]); /** * Cleanup animation frame on unmount */ useCallback(() => { return () => { if (animationFrameRef.current) { cancelAnimationFrame(animationFrameRef.current); } }; }, []); return { transform, style: getStyle(), setRotation, setTranslation, setScale, updateTransform, rotateFromPointer, animateRotation, reset, flip, wobble, getTransformString }; }; export { use3DTransform }; //# sourceMappingURL=use3DTransform.js.map