lightswind
Version:
A professionally designed animate react component library & templates market that brings together functionality, accessibility, and beautiful aesthetics for modern applications.
130 lines (129 loc) • 7.4 kB
JavaScript
"use client";
import { jsx as _jsx } from "react/jsx-runtime";
import { useEffect, useRef, useState } from "react";
import { motion, AnimatePresence, useMotionValue, } from "framer-motion";
/**
* ThreeDImageGallery Component
* Renders a responsive 3D image gallery with interactive and auto-rotate features.
*/
const ThreeDImageGallery = ({
// Default images if none are provided
images = [
"https://images.pexels.com/photos/2514035/pexels-photo-2514035.jpeg?auto=compress&cs=tinysrgb&w=800",
"https://images.pexels.com/photos/816608/pexels-photo-816608.jpeg?auto=compress&cs=tinysrgb&w=800",
"https://images.pexels.com/photos/1271620/pexels-photo-1271620.jpeg?auto=compress&cs=tinysrgb&w=800",
"https://images.pexels.com/photos/1779487/pexels-photo-1779487.jpeg?auto=compress&cs=tinysrgb&w=800",
"https://images.pexels.com/photos/1105666/pexels-photo-1105666.jpeg?auto=compress&cs=tinysrgb&w=800",
], width = 800, // Default width for the gallery container
height = 300, // Default height for the gallery container
spacing = 1.5, // Default spacing multiplier
rotationAngle = 0.1, // Default rotation angle multiplier
borderRadius = 0.1, // Default border radius multiplier
autoRotate = false, // Default auto-rotate off
autoRotateSpeed = 0.3, // Default auto-rotate speed
className = "", // Default empty class name
style = {}, // Default empty style object
onImageClick, // Image click handler
}) => {
// State to manage the currently active (front-most) image index
const [activeIndex, setActiveIndex] = useState(Math.floor(images.length / 2));
// Ref for the main container div to get its dimensions for mouse interactions
const containerRef = useRef(null);
// Motion values for X and Y rotation based on mouse position
const rotateX = useMotionValue(0);
const rotateY = useMotionValue(0);
// Effect to handle auto-rotation
useEffect(() => {
let intervalId = null; // Local variable to store the interval ID
if (autoRotate) {
// Set up an interval to change the active image index
intervalId = setInterval(() => {
setActiveIndex((prev) => (prev + 1) % images.length);
}, autoRotateSpeed * 1000); // Convert speed to milliseconds
}
// Clean up function for the effect
return () => {
if (intervalId) {
clearInterval(intervalId); // Clear the interval if it was set
}
};
}, [autoRotate, autoRotateSpeed, images.length]); // Dependencies for the effect
// Handler for mouse movement over the gallery container
const handleMouseMove = (e) => {
// Get the bounding rectangle of the container
const { left, top, width, height } = e.currentTarget.getBoundingClientRect();
// Calculate mouse position relative to the container
const x = e.clientX - left;
const y = e.clientY - top;
// Calculate rotation values based on mouse position
// Normalized x/y (0 to 1) then mapped to a rotation range (-10 to 10 degrees)
const rotateYVal = (x / width - 0.5) * 20;
const rotateXVal = (y / height - 0.5) * -20;
// Update motion values
rotateX.set(rotateXVal);
rotateY.set(rotateYVal);
};
// Handler for mouse leaving the gallery container
const handleMouseLeave = () => {
// Reset rotation to 0 when mouse leaves
rotateX.set(0);
rotateY.set(0);
};
return (
// Main container for the 3D gallery
_jsx(motion.div, { ref: containerRef, className: `relative flex items-center justify-center ${className}`, style: {
width, // Apply width from props
height, // Apply height from props
perspective: 1200, // Apply perspective for 3D effect
transformStyle: "preserve-3d", // Preserve 3D transformations for children
...style, // Apply any additional inline styles
}, onMouseMove: handleMouseMove, onMouseLeave: handleMouseLeave, children: _jsx(motion.div, { style: {
rotateX, // Apply X rotation from motion value
rotateY, // Apply Y rotation from motion value
transformStyle: "preserve-3d", // Preserve 3D transformations for children
}, className: "relative", children: _jsx(AnimatePresence, { initial: false, children: images.map((img, index) => {
// Calculate offset from the active image
const offset = index - activeIndex;
// Scale of the image (active image is full size, others are smaller)
const scale = index === activeIndex ? 1 : 0.8;
// Z-index to ensure correct layering in 3D space
const zIndex = -Math.abs(offset);
// X translation based on offset and spacing
const x = offset * spacing * 200; // 200 is the fixed width of an image
// Y rotation for each image to create the 3D fan effect
const rotateYImage = offset * -rotationAngle * 180;
return (
// Individual image container with motion properties
_jsx(motion.div, { className: "absolute rounded-lg overflow-hidden shadow-xl cursor-pointer", style: {
width: 200, // Fixed width for each image
height: 280, // Fixed height for each image
borderRadius: `${borderRadius * 100}px`, // Apply border radius
zIndex, // Apply calculated z-index
},
// Animation properties for image position, scale, rotation, and opacity
animate: {
x,
scale,
rotateY: rotateYImage,
opacity: Math.abs(offset) > 2 ? 0 : 1, // Hide images that are too far off-center
filter: index === activeIndex ? "brightness(1)" : "brightness(0.7)", // Dim non-active images
},
// Spring transition for smooth animations
transition: { type: "spring", stiffness: 150, damping: 20 },
// Click handler for images
onClick: () => {
// If a non-active image is clicked, make it active
if (index !== activeIndex)
return setActiveIndex(index);
// If the active image is clicked, trigger the onImageClick prop
onImageClick?.(index);
}, children: _jsx("img", { src: img, alt: `gallery-image-${index}`, className: "w-full h-full object-cover" // Ensure image covers its container
,
// Fallback for broken image URLs
onError: (e) => {
e.currentTarget.src = `https://placehold.co/200x280/cccccc/333333?text=Image+Error`;
e.currentTarget.onerror = null; // Prevent infinite loop on error
} }) }, img));
}) }) }) }));
};
export default ThreeDImageGallery;