lightswind
Version:
A collection of beautifully crafted React Components, Blocks & Templates for Modern Developers. Create stunning web applications effortlessly by using our 160+ professional and animated react components.
180 lines • 9.94 kB
JavaScript
;
"use client";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ThreeDImageRing = ThreeDImageRing;
const jsx_runtime_1 = require("react/jsx-runtime");
const react_1 = require("react");
const framer_motion_1 = require("framer-motion");
const utils_1 = require("@/components/lib/utils"); // Assuming you have this utility for class names
const framer_motion_2 = require("framer-motion");
function ThreeDImageRing({ images = [
"https://images.unsplash.com/photo-1510812431401-41d2bd2722f3?q=80&w=2940&auto=format&fit=crop",
"https://images.unsplash.com/photo-1469474968028-56623f02e42e?q=80&w=2938&auto=format&fit=crop",
"https://images.unsplash.com/photo-1506744626753-1fa7604d459a?q=80&w=2940&auto=format&fit=crop",
"https://images.unsplash.com/photo-1470071131384-001b85755536?q=80&w=2940&auto=format&fit=crop",
"https://images.unsplash.com/photo-1472214103451-9374bd1c798e?q=80&w=2940&auto=format&fit=crop",
], width = 300, perspective = 2000, imageDistance = 500, initialRotation = 180, animationDuration = 1.5, staggerDelay = 0.1, hoverOpacity = 0.5, containerClassName, ringClassName, imageClassName, backgroundColor, draggable = true, ease = "easeOut", mobileBreakpoint = 768, mobileScaleFactor = 0.8, inertiaPower = 0.8, // Default power for inertia
inertiaTimeConstant = 300, // Default time constant for inertia
inertiaVelocityMultiplier = 20, // Default multiplier for initial spin
}) {
const containerRef = (0, react_1.useRef)(null);
const ringRef = (0, react_1.useRef)(null);
const rotationY = (0, framer_motion_1.useMotionValue)(initialRotation);
const startX = (0, react_1.useRef)(0);
const currentRotationY = (0, react_1.useRef)(initialRotation);
const isDragging = (0, react_1.useRef)(false);
const velocity = (0, react_1.useRef)(0); // To track drag velocity
const [currentScale, setCurrentScale] = (0, react_1.useState)(1);
const [showImages, setShowImages] = (0, react_1.useState)(false);
const angle = (0, react_1.useMemo)(() => 360 / images.length, [images.length]);
const getBgPos = (imageIndex, currentRot, scale) => {
const scaledImageDistance = imageDistance * scale;
const effectiveRotation = currentRot - 180 - imageIndex * angle;
const parallaxOffset = ((effectiveRotation % 360 + 360) % 360) / 360;
return `${-(parallaxOffset * (scaledImageDistance / 1.5))}px 0px`;
};
(0, react_1.useEffect)(() => {
const unsubscribe = rotationY.on("change", (latestRotation) => {
if (ringRef.current) {
Array.from(ringRef.current.children).forEach((imgElement, i) => {
imgElement.style.backgroundPosition = getBgPos(i, latestRotation, currentScale);
});
}
currentRotationY.current = latestRotation;
});
return () => unsubscribe();
}, [rotationY, images.length, imageDistance, currentScale, angle]);
(0, react_1.useEffect)(() => {
const handleResize = () => {
const viewportWidth = window.innerWidth;
const newScale = viewportWidth <= mobileBreakpoint ? mobileScaleFactor : 1;
setCurrentScale(newScale);
};
window.addEventListener("resize", handleResize);
handleResize();
return () => window.removeEventListener("resize", handleResize);
}, [mobileBreakpoint, mobileScaleFactor]);
(0, react_1.useEffect)(() => {
setShowImages(true);
}, []);
const handleDragStart = (event) => {
if (!draggable)
return;
isDragging.current = true;
const clientX = "touches" in event ? event.touches[0].clientX : event.clientX;
startX.current = clientX;
// Stop any ongoing animation instantly when drag starts
rotationY.stop();
velocity.current = 0; // Reset velocity
if (ringRef.current) {
ringRef.current.style.cursor = "grabbing";
}
// Attach global move and end listeners to document when dragging starts
document.addEventListener("mousemove", handleDrag);
document.addEventListener("mouseup", handleDragEnd);
document.addEventListener("touchmove", handleDrag);
document.addEventListener("touchend", handleDragEnd);
};
const handleDrag = (event) => {
// Only proceed if dragging is active
if (!draggable || !isDragging.current)
return;
const clientX = "touches" in event ? event.touches[0].clientX : event.clientX;
const deltaX = clientX - startX.current;
// Update velocity based on deltaX
velocity.current = -deltaX * 0.5; // Factor of 0.5 to control sensitivity
rotationY.set(currentRotationY.current + velocity.current);
startX.current = clientX;
};
const handleDragEnd = () => {
isDragging.current = false;
if (ringRef.current) {
ringRef.current.style.cursor = "grab";
currentRotationY.current = rotationY.get();
}
document.removeEventListener("mousemove", handleDrag);
document.removeEventListener("mouseup", handleDragEnd);
document.removeEventListener("touchmove", handleDrag);
document.removeEventListener("touchend", handleDragEnd);
const initial = rotationY.get();
const velocityBoost = velocity.current * inertiaVelocityMultiplier;
const target = initial + velocityBoost;
// Animate with inertia manually using `animate()`
(0, framer_motion_2.animate)(initial, target, {
type: "inertia",
velocity: velocityBoost,
power: inertiaPower,
timeConstant: inertiaTimeConstant,
restDelta: 0.5,
modifyTarget: (target) => Math.round(target / angle) * angle,
onUpdate: (latest) => {
rotationY.set(latest);
},
});
velocity.current = 0;
};
// Corrected imageVariants: no function for 'visible' state
const imageVariants = {
hidden: { y: 200, opacity: 0 },
visible: {
y: 0,
opacity: 1,
// Transition properties will be defined directly on the motion.div using `custom` prop
},
};
return ((0, jsx_runtime_1.jsx)("div", { ref: containerRef, className: (0, utils_1.cn)("w-full h-full overflow-hidden select-none relative", containerClassName), style: {
backgroundColor,
transform: `scale(${currentScale})`,
transformOrigin: "center center",
},
// Attach initial drag start listeners only
onMouseDown: draggable ? handleDragStart : undefined, onTouchStart: draggable ? handleDragStart : undefined, children: (0, jsx_runtime_1.jsx)("div", { style: {
perspective: `${perspective}px`,
width: `${width}px`,
height: `${width * 1.33}px`,
position: "absolute",
left: "50%",
top: "50%",
transform: "translate(-50%, -50%)",
}, children: (0, jsx_runtime_1.jsx)(framer_motion_1.motion.div, { ref: ringRef, className: (0, utils_1.cn)("w-full h-full absolute", ringClassName), style: {
transformStyle: "preserve-3d",
rotateY: rotationY,
cursor: draggable ? "grab" : "default",
}, children: (0, jsx_runtime_1.jsx)(framer_motion_1.AnimatePresence, { children: showImages && images.map((imageUrl, index) => ((0, jsx_runtime_1.jsx)(framer_motion_1.motion.div, { className: (0, utils_1.cn)("w-full h-full absolute", imageClassName), style: {
transformStyle: "preserve-3d",
backgroundImage: `url(${imageUrl})`,
backgroundSize: "cover",
backgroundRepeat: "no-repeat",
backfaceVisibility: "hidden",
rotateY: index * -angle,
z: -imageDistance * currentScale,
transformOrigin: `50% 50% ${imageDistance * currentScale}px`,
backgroundPosition: getBgPos(index, currentRotationY.current, currentScale),
}, initial: "hidden", animate: "visible", exit: "hidden", variants: imageVariants, custom: index, transition: {
delay: index * staggerDelay, // Use index directly in transition
duration: animationDuration,
ease: framer_motion_1.easeOut, // Apply ease for entrance animation
}, whileHover: { opacity: 1, transition: { duration: 0.15 } }, onHoverStart: () => {
// Prevent hover effects while dragging
if (isDragging.current)
return;
if (ringRef.current) {
Array.from(ringRef.current.children).forEach((imgEl, i) => {
if (i !== index) {
imgEl.style.opacity = `${hoverOpacity}`;
}
});
}
}, onHoverEnd: () => {
// Prevent hover effects while dragging
if (isDragging.current)
return;
if (ringRef.current) {
Array.from(ringRef.current.children).forEach((imgEl) => {
imgEl.style.opacity = `1`;
});
}
} }, index))) }) }) }) }));
}
exports.default = ThreeDImageRing;
//# sourceMappingURL=3d-image-ring.js.map