UNPKG

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
// @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;