aura-glass
Version:
A comprehensive glassmorphism design system for React applications with 142+ production-ready components
231 lines (228 loc) • 6.51 kB
JavaScript
'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