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.
128 lines (127 loc) • 5.9 kB
JavaScript
// @ts-nocheck
// ThreeDScrollTrigger.tsx
"use client";
import { jsx as _jsx } from "react/jsx-runtime";
import React, { useRef, useEffect, useState, useMemo, useContext, } from "react";
import { motion, useAnimationFrame, useInView, useMotionValue, useScroll, useSpring, useTransform, useVelocity, } from "framer-motion";
import { cn } from "../../lib/utils";
/* -------------------------
Utility: wrap (unchanged)
------------------------- */
export const wrap = (min, max, v) => {
const rangeSize = max - min;
return ((((v - min) % rangeSize) + rangeSize) % rangeSize) + min;
};
/* -----------------------------------
Context to share velocity between rows
----------------------------------- */
const ThreeDScrollTriggerContext = React.createContext(null);
/* --------------------------
Container that provides velocity
-------------------------- */
export function ThreeDScrollTriggerContainer({ children, className, ...props }) {
const { scrollY } = useScroll();
const scrollVelocity = useVelocity(scrollY);
const smoothVelocity = useSpring(scrollVelocity, {
damping: 50,
stiffness: 400,
});
// map to a bounded factor [-5...5] with smoother behaviour
const velocityFactor = useTransform(smoothVelocity, (v) => {
const sign = v < 0 ? -1 : 1;
const magnitude = Math.min(5, (Math.abs(v) / 1000) * 5);
return sign * magnitude;
});
return (_jsx(ThreeDScrollTriggerContext.Provider, { value: velocityFactor, children: _jsx("div", { className: cn("relative w-full", className), ...props, children: children }) }));
}
/* --------------------------
Row entry that chooses shared or local velocity
-------------------------- */
export function ThreeDScrollTriggerRow(props) {
const sharedVelocityFactor = useContext(ThreeDScrollTriggerContext);
if (sharedVelocityFactor) {
return (_jsx(ThreeDScrollTriggerRowImpl, { ...props, velocityFactor: sharedVelocityFactor }));
}
return _jsx(ThreeDScrollTriggerRowLocal, { ...props });
}
function ThreeDScrollTriggerRowImpl({ children, baseVelocity = 5, direction = 1, className, velocityFactor, ...props }) {
const containerRef = useRef(null);
const [numCopies, setNumCopies] = useState(3);
const x = useMotionValue(0);
const prevTimeRef = useRef(null);
const unitWidthRef = useRef(0);
const baseXRef = useRef(0);
// Memoized children
const childrenArray = useMemo(() => React.Children.toArray(children), [children]);
const BlockContent = useMemo(() => {
return (_jsx("div", { className: "inline-flex shrink-0", style: { contain: "paint" }, children: childrenArray }));
}, [childrenArray]);
// Measure block width
useEffect(() => {
const container = containerRef.current;
if (!container)
return;
const block = container.querySelector(".threed-scroll-trigger-block");
if (block) {
unitWidthRef.current = block.scrollWidth;
// keep just enough to cover the viewport + 1
const containerWidth = container.offsetWidth;
const needed = Math.max(3, Math.ceil(containerWidth / unitWidthRef.current) + 2);
setNumCopies(needed);
}
}, [childrenArray]);
// Optimize: Check if container is in view
const isInView = useInView(containerRef, { margin: "20%" });
// Animation loop
useAnimationFrame((time) => {
if (!isInView)
return;
if (prevTimeRef.current == null)
prevTimeRef.current = time;
const dt = Math.max(0, (time - prevTimeRef.current) / 1000);
prevTimeRef.current = time;
const unitWidth = unitWidthRef.current;
if (unitWidth <= 0)
return;
const velocity = velocityFactor.get();
const speedMultiplier = Math.min(5, Math.abs(velocity));
const scrollDirection = velocity >= 0 ? 1 : -1;
const currentDirection = direction * scrollDirection;
const pixelsPerSecond = (unitWidth * baseVelocity) / 100;
const moveBy = currentDirection * pixelsPerSecond * (1 + speedMultiplier) * dt;
const newX = baseXRef.current + moveBy;
// ✅ FIXED: Proper wrapping in both directions
// When moving right (positive newX), wrap back
if (newX >= unitWidth) {
baseXRef.current = newX % unitWidth;
}
// When moving left (negative newX), wrap forward
else if (newX <= 0) {
baseXRef.current = unitWidth + (newX % unitWidth);
}
else {
baseXRef.current = newX;
}
x.set(baseXRef.current);
});
const xTransform = useTransform(x, (v) => `translate3d(${-v}px,0,0)`);
return (_jsx("div", { ref: containerRef, className: cn("w-full overflow-hidden whitespace-nowrap", className), ...props, children: _jsx(motion.div, { className: "inline-flex will-change-transform transform-gpu", style: { transform: xTransform }, children: Array.from({ length: numCopies }).map((_, i) => (_jsx("div", { className: cn("inline-flex shrink-0", i === 0 ? "threed-scroll-trigger-block" : ""), children: BlockContent }, i))) }) }));
}
/* --------------------------
Local row (if no shared velocity)
-------------------------- */
function ThreeDScrollTriggerRowLocal(props) {
const { scrollY } = useScroll();
const localVelocity = useVelocity(scrollY);
const localSmoothVelocity = useSpring(localVelocity, {
damping: 50,
stiffness: 400,
});
const localVelocityFactor = useTransform(localSmoothVelocity, (v) => {
const sign = v < 0 ? -1 : 1;
const magnitude = Math.min(5, (Math.abs(v) / 1000) * 5);
return sign * magnitude;
});
return (_jsx(ThreeDScrollTriggerRowImpl, { ...props, velocityFactor: localVelocityFactor }));
}
export default ThreeDScrollTriggerRow;