Skip to content

Commit

Permalink
fix: update velocity scroll component
Browse files Browse the repository at this point in the history
  • Loading branch information
dillionverma committed Dec 27, 2024
1 parent 6e9e761 commit 3ab3dab
Show file tree
Hide file tree
Showing 3 changed files with 99 additions and 101 deletions.
12 changes: 6 additions & 6 deletions content/docs/components/scroll-based-velocity.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,9 @@ npm install framer-motion

## Props

| Prop | Type | Description | Default |
| ---------------- | ------ | --------------------------------------------- | ------- |
| className | string | The class name to be applied to the component | |
| text | string | Text to be animated | "" |
| default_velocity | number | Base scroll velocity of text | 5 |
| rowsAmount | number | Number of rows to be animated | 1 |
| Prop | Type | Description | Default |
| --------------- | --------- | --------------------------------------------- | ------- |
| className | string | The class name to be applied to the component | |
| children | ReactNode | Content to be animated | |
| defaultVelocity | number | Base scroll velocity of text | 5 |
| numRows | number | Number of rows to be animated | 2 |
9 changes: 1 addition & 8 deletions registry/default/example/scroll-based-velocity-demo.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,5 @@
import { VelocityScroll } from "@/registry/default/magicui/scroll-based-velocity";

export default function ScrollBasedVelocityDemo() {
return (
<VelocityScroll
text="Velocity Scroll"
default_velocity={5}
rowsAmount={2}
className="font-display text-center text-4xl font-bold tracking-[-0.02em] text-black drop-shadow-sm dark:text-white md:text-7xl md:leading-[5rem]"
/>
);
return <VelocityScroll>Velocity Scroll</VelocityScroll>;
}
179 changes: 92 additions & 87 deletions registry/default/magicui/scroll-based-velocity.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
"use client";

import React, { useEffect, useRef, useState } from "react";
import {
motion,
useAnimationFrame,
Expand All @@ -10,114 +9,120 @@ import {
useTransform,
useVelocity,
} from "framer-motion";
import React, { useEffect, useRef, useState } from "react";

import { cn } from "@/lib/utils";

interface VelocityScrollProps {
text: string;
default_velocity?: number;
interface VelocityScrollProps extends React.HTMLAttributes<HTMLDivElement> {
defaultVelocity?: number;
className?: string;
rowsAmount?: number;
numRows?: number;
}

interface ParallaxProps {
children: string;
interface ParallaxProps extends React.HTMLAttributes<HTMLDivElement> {
children: React.ReactNode;
baseVelocity: number;
className?: string;
}

export const wrap = (min: number, max: number, v: number) => {
const rangeSize = max - min;
return ((((v - min) % rangeSize) + rangeSize) % rangeSize) + min;
};

export function VelocityScroll({
text,
default_velocity = 5,
className,
rowsAmount = 1,
}: VelocityScrollProps) {
function ParallaxText({
children,
baseVelocity = 100,
className,
}: ParallaxProps) {
const baseX = useMotionValue(0);
const { scrollY } = useScroll();
const scrollVelocity = useVelocity(scrollY);
const smoothVelocity = useSpring(scrollVelocity, {
damping: 50,
stiffness: 400,
});

const velocityFactor = useTransform(smoothVelocity, [0, 1000], [0, 5], {
clamp: false,
});

const [repetitions, setRepetitions] = useState(1);
const containerRef = useRef<HTMLDivElement>(null);
const textRef = useRef<HTMLSpanElement>(null);

useEffect(() => {
const calculateRepetitions = () => {
if (containerRef.current && textRef.current) {
const containerWidth = containerRef.current.offsetWidth;
const textWidth = textRef.current.offsetWidth;
const newRepetitions = Math.ceil(containerWidth / textWidth) + 2;
setRepetitions(newRepetitions);
}
};

calculateRepetitions();

window.addEventListener("resize", calculateRepetitions);
return () => window.removeEventListener("resize", calculateRepetitions);
}, [children]);

const x = useTransform(baseX, (v) => `${wrap(-100 / repetitions, 0, v)}%`);

const directionFactor = React.useRef<number>(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;
function ParallaxText({
children,
baseVelocity = 100,
...props
}: ParallaxProps) {
const baseX = useMotionValue(0);
const { scrollY } = useScroll();
const scrollVelocity = useVelocity(scrollY);
const smoothVelocity = useSpring(scrollVelocity, {
damping: 50,
stiffness: 400,
});

const velocityFactor = useTransform(smoothVelocity, [0, 1000], [0, 5], {
clamp: false,
});

const [repetitions, setRepetitions] = useState(1);
const containerRef = useRef<HTMLDivElement>(null);
const textRef = useRef<HTMLSpanElement>(null);

useEffect(() => {
const calculateRepetitions = () => {
if (containerRef.current && textRef.current) {
const containerWidth = containerRef.current.offsetWidth;
const textWidth = textRef.current.offsetWidth;
const newRepetitions = Math.ceil(containerWidth / textWidth) + 2;
setRepetitions(newRepetitions);
}
};

calculateRepetitions();

window.addEventListener("resize", calculateRepetitions);
return () => window.removeEventListener("resize", calculateRepetitions);
}, [children]);

const x = useTransform(baseX, (v) => `${wrap(-100 / repetitions, 0, v)}%`);

const directionFactor = React.useRef<number>(1);
useAnimationFrame((t, delta) => {
let moveBy = directionFactor.current * baseVelocity * (delta / 1000);

moveBy += directionFactor.current * moveBy * velocityFactor.get();

baseX.set(baseX.get() + moveBy);
});

return (
<div
className="w-full overflow-hidden whitespace-nowrap"
ref={containerRef}
>
<motion.div className={cn("inline-block", className)} style={{ x }}>
{Array.from({ length: repetitions }).map((_, i) => (
<span key={i} ref={i === 0 ? textRef : null}>
{children}{" "}
</span>
))}
</motion.div>
</div>
);
}
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);
});

return (
<div
ref={containerRef}
className="w-full overflow-hidden whitespace-nowrap"
{...props}
>
<motion.div className="inline-block" style={{ x }}>
{Array.from({ length: repetitions }).map((_, i) => (
<span key={i} ref={i === 0 ? textRef : null}>
{children}{" "}
</span>
))}
</motion.div>
</div>
);
}

export function VelocityScroll({
defaultVelocity = 5,
numRows = 2,
children,
className,
...props
}: VelocityScrollProps) {
return (
<section className="relative w-full">
{Array.from({ length: rowsAmount }).map((_, i) => (
<div
className={cn(
"relative w-full text-4xl font-bold tracking-[-0.02em] md:text-7xl md:leading-[5rem]",
className,
)}
{...props}
>
{Array.from({ length: numRows }).map((_, i) => (
<ParallaxText
key={i}
baseVelocity={default_velocity * (i % 2 === 0 ? 1 : -1)}
className={className}
baseVelocity={defaultVelocity * (i % 2 === 0 ? 1 : -1)}
>
{text}
{children}
</ParallaxText>
))}
</section>
</div>
);
}

0 comments on commit 3ab3dab

Please sign in to comment.