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.
125 lines (124 loc) • 6.13 kB
JavaScript
// @ts-nocheck
"use client";
import { jsx as _jsx } from "react/jsx-runtime";
import { motion, useAnimationFrame, useInView, useMotionValue, useScroll, useSpring, useTransform, useVelocity, } from "framer-motion";
import React, { useContext, useEffect, useRef, useState } from "react";
// Assuming you have a utility function for Tailwind CSS class merging
const cn = (...classes) => classes.filter(Boolean).join(' ');
/**
* Utility function to wrap a value within a min/max range for continuous looping.
* @param min The minimum value of the range.
* @param max The maximum value of the range.
* @param v The value to wrap.
*/
export const wrap = (min, max, v) => {
const rangeSize = max - min;
return ((((v - min) % rangeSize) + rangeSize) % rangeSize) + min;
};
// --- Context for Shared Scroll Velocity ---
const ScrollVelocityContext = React.createContext(null);
// --- ScrollVelocityContainer (Parent Component) ---
export function ScrollVelocityContainer({ children, className, ...props }) {
const { scrollY } = useScroll();
const scrollVelocity = useVelocity(scrollY);
const smoothVelocity = useSpring(scrollVelocity, {
damping: 50,
stiffness: 400,
});
// Transforms the smooth scroll velocity into a factor used to control the marquee speed.
const velocityFactor = useTransform(smoothVelocity, (v) => {
const sign = v < 0 ? -1 : 1;
// Clamps the magnitude to a max of 5 for reasonable speed
const magnitude = Math.min(5, (Math.abs(v) / 1000) * 5);
return sign * magnitude;
});
return (_jsx(ScrollVelocityContext.Provider, { value: velocityFactor, children: _jsx("div", { className: cn("relative w-full", className), ...props, children: children }) }));
}
// --- ScrollVelocityRow (Router) ---
// Decides whether to use shared context or local velocity calculation.
export function ScrollVelocityRow(props) {
const sharedVelocityFactor = useContext(ScrollVelocityContext);
if (sharedVelocityFactor) {
return (_jsx(ScrollVelocityRowImpl, { ...props, velocityFactor: sharedVelocityFactor }));
}
return _jsx(ScrollVelocityRowLocal, { ...props });
}
function ScrollVelocityRowImpl({ children, baseVelocity = 5, direction = 1, className, velocityFactor, ...props }) {
const containerRef = useRef(null);
const [numCopies, setNumCopies] = useState(1);
const x = useMotionValue(0);
// State added to handle pause on hover
const [isHovered, setIsHovered] = useState(false);
const prevTimeRef = useRef(0);
const unitWidthRef = useRef(0);
const baseXRef = useRef(0);
// Optimize: Check if container is in view
const isInView = useInView(containerRef, { margin: "20%" });
// ResizeObserver to determine how many copies are needed for continuous loop
useEffect(() => {
const container = containerRef.current;
if (!container)
return;
const ro = new ResizeObserver(([entry]) => {
const containerWidth = entry.contentRect.width;
const block = container.querySelector(".scroll-velocity-block");
if (!block)
return;
const blockWidth = block.scrollWidth;
unitWidthRef.current = blockWidth;
if (blockWidth > 0) {
// Calculate copies: need at least 3, or enough to cover the screen plus padding
const nextCopies = Math.max(3, Math.ceil(containerWidth / blockWidth) + 2);
setNumCopies(nextCopies);
}
});
ro.observe(container);
return () => ro.disconnect();
}, []);
// Frame-by-frame animation logic
useAnimationFrame((time) => {
if (!isInView || isHovered) {
prevTimeRef.current = time;
return;
}
const dt = (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;
// Calculate how far to move this frame
const pixelsPerSecond = (unitWidth * baseVelocity) / 100;
const moveBy = currentDirection * pixelsPerSecond * (1 + speedMultiplier) * dt;
// Update position and apply the wrap function for seamless looping
const newX = baseXRef.current + moveBy;
baseXRef.current = wrap(0, unitWidth, newX);
x.set(baseXRef.current);
});
const childrenArray = React.Children.toArray(children);
return (_jsx("div", { ref: containerRef, className: cn("w-full overflow-hidden whitespace-nowrap", className), ...props,
// Handlers to control the hover state
onMouseEnter: () => setIsHovered(true), onMouseLeave: () => setIsHovered(false), children: _jsx(motion.div, { className: "inline-flex will-change-transform transform-gpu",
// Use useTransform to invert the x value for a left-to-right scroll effect
style: { x: useTransform(x, (v) => `${-v}px`) }, children: Array.from({ length: numCopies }).map((_, i) => (_jsx("div", { className: cn("inline-flex shrink-0",
// Mark the first copy to measure its width for looping
i === 0 && "scroll-velocity-block"), "aria-hidden": i !== 0, children: childrenArray }, i))) }) }));
}
// --- ScrollVelocityRowLocal (Fallback for standalone usage) ---
function ScrollVelocityRowLocal(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(ScrollVelocityRowImpl, { ...props, velocityFactor: localVelocityFactor }));
}