reactbits-mcp-server
Version:
MCP Server for React Bits - Access 99+ React components with animations, backgrounds, and UI elements
147 lines (132 loc) • 3.7 kB
JSX
import { useRef, useLayoutEffect, useState } from "react";
import {
motion,
useScroll,
useSpring,
useTransform,
useMotionValue,
useVelocity,
useAnimationFrame,
} from "framer-motion";
import "./ScrollVelocity.css";
function useElementWidth(ref) {
const [width, setWidth] = useState(0);
useLayoutEffect(() => {
function updateWidth() {
if (ref.current) {
setWidth(ref.current.offsetWidth);
}
}
updateWidth();
window.addEventListener("resize", updateWidth);
return () => window.removeEventListener("resize", updateWidth);
}, [ref]);
return width;
}
export const ScrollVelocity = ({
scrollContainerRef,
texts = [],
velocity = 100,
className = "",
damping = 50,
stiffness = 400,
numCopies = 6,
velocityMapping = { input: [0, 1000], output: [0, 5] },
parallaxClassName = "parallax",
scrollerClassName = "scroller",
parallaxStyle,
scrollerStyle,
}) => {
function VelocityText({
children,
baseVelocity = velocity,
scrollContainerRef,
className = "",
damping,
stiffness,
numCopies,
velocityMapping,
parallaxClassName,
scrollerClassName,
parallaxStyle,
scrollerStyle,
}) {
const baseX = useMotionValue(0);
const scrollOptions = scrollContainerRef ? { container: scrollContainerRef } : {};
const { scrollY } = useScroll(scrollOptions);
const scrollVelocity = useVelocity(scrollY);
const smoothVelocity = useSpring(scrollVelocity, {
damping: damping ?? 50,
stiffness: stiffness ?? 400,
});
const velocityFactor = useTransform(
smoothVelocity,
velocityMapping?.input || [0, 1000],
velocityMapping?.output || [0, 5],
{ clamp: false }
);
const copyRef = useRef(null);
const copyWidth = useElementWidth(copyRef);
function wrap(min, max, v) {
const range = max - min;
const mod = (((v - min) % range) + range) % range;
return mod + min;
}
const x = useTransform(baseX, (v) => {
if (copyWidth === 0) return "0px";
return `${wrap(-copyWidth, 0, v)}px`;
});
const directionFactor = useRef(1);
useAnimationFrame((t, delta) => {
let moveBy = directionFactor.current * baseVelocity * (delta / 1000);
if (velocityFactor.get() < 0) {
directionFactor.current = -1;
} else if (velocityFactor.get() > 0) {
directionFactor.current = 1;
}
moveBy += directionFactor.current * moveBy * velocityFactor.get();
baseX.set(baseX.get() + moveBy);
});
const spans = [];
for (let i = 0; i < numCopies; i++) {
spans.push(
<span className={className} key={i} ref={i === 0 ? copyRef : null}>
{children}
</span>
);
}
return (
<div className={parallaxClassName} style={parallaxStyle}>
<motion.div
className={scrollerClassName}
style={{ x, ...scrollerStyle }}
>
{spans}
</motion.div>
</div>
);
}
return (
<section>
{texts.map((text, index) => (
<VelocityText
key={index}
className={className}
baseVelocity={index % 2 !== 0 ? -velocity : velocity}
scrollContainerRef={scrollContainerRef}
damping={damping}
stiffness={stiffness}
numCopies={numCopies}
velocityMapping={velocityMapping}
parallaxClassName={parallaxClassName}
scrollerClassName={scrollerClassName}
parallaxStyle={parallaxStyle}
scrollerStyle={scrollerStyle}
>
{text}
</VelocityText>
))}
</section>
);
};
export default ScrollVelocity;