From c404c121d504966a338c65519ce8f8971c9c4b4d Mon Sep 17 00:00:00 2001 From: Aniket Saha Date: Sun, 1 Mar 2026 14:03:59 +0530 Subject: [PATCH] feat: updated Scroll Based Velocity component to disable scroll prop for infinite text scrolling --- .../docs/components/scroll-based-velocity.mdx | 13 +++++++------ apps/www/public/llms-full.txt | 4 +++- apps/www/public/r/scroll-based-velocity.json | 2 +- apps/www/registry/magicui/scroll-based-velocity.tsx | 4 +++- 4 files changed, 14 insertions(+), 9 deletions(-) diff --git a/apps/www/content/docs/components/scroll-based-velocity.mdx b/apps/www/content/docs/components/scroll-based-velocity.mdx index c7552d18e..8e6db0293 100644 --- a/apps/www/content/docs/components/scroll-based-velocity.mdx +++ b/apps/www/content/docs/components/scroll-based-velocity.mdx @@ -79,12 +79,13 @@ import { ### ScrollVelocityRow -| Prop | Type | Default | Description | -| -------------- | ----------- | ------- | -------------------------------------------------- | -| `className` | `string` | `-` | The class name to be applied to the row container | -| `children` | `ReactNode` | `-` | Content to be duplicated and scrolled | -| `baseVelocity` | `number` | `5` | Base scroll velocity percentage of content width | -| `direction` | `1 \| -1` | `1` | Scroll direction (1 = left-to-right, -1 = reverse) | +| Prop | Type | Default | Description | +| ------------------ | ----------- | ------- | -------------------------------------------------- | +| `className` | `string` | `-` | The class name to be applied to the row container | +| `children` | `ReactNode` | `-` | Content to be duplicated and scrolled | +| `baseVelocity` | `number` | `5` | Base scroll velocity percentage of content width | +| `direction` | `1 \| -1` | `1` | Scroll direction (1 = left-to-right, -1 = reverse) | +| `scrollReactivity` | `boolean` | `true` | Toggles scroll interactivity | ## Performance diff --git a/apps/www/public/llms-full.txt b/apps/www/public/llms-full.txt index 505dbf613..05361005d 100644 --- a/apps/www/public/llms-full.txt +++ b/apps/www/public/llms-full.txt @@ -12516,6 +12516,7 @@ interface ScrollVelocityRowProps extends React.HTMLAttributes { children: React.ReactNode baseVelocity?: number direction?: 1 | -1 + scrollReactivity?: boolean } export const wrap = (min: number, max: number, v: number) => { @@ -12573,6 +12574,7 @@ function ScrollVelocityRowImpl({ direction = 1, className, velocityFactor, + scrollReactivity = true, ...props }: ScrollVelocityRowImplProps) { const containerRef = useRef(null) @@ -12644,7 +12646,7 @@ function ScrollVelocityRowImpl({ useAnimationFrame((_, delta) => { if (!isInViewRef.current || !isPageVisibleRef.current) return const dt = delta / 1000 - const vf = velocityFactor.get() + const vf = scrollReactivity ? velocityFactor.get() : 0 const absVf = Math.min(5, Math.abs(vf)) const speedMultiplier = prefersReducedMotionRef.current ? 1 : 1 + absVf diff --git a/apps/www/public/r/scroll-based-velocity.json b/apps/www/public/r/scroll-based-velocity.json index 807dbbec9..39aa940ac 100644 --- a/apps/www/public/r/scroll-based-velocity.json +++ b/apps/www/public/r/scroll-based-velocity.json @@ -10,7 +10,7 @@ "files": [ { "path": "registry/magicui/scroll-based-velocity.tsx", - "content": "\"use client\"\n\nimport React, { useContext, useEffect, useRef, useState } from \"react\"\nimport {\n motion,\n useAnimationFrame,\n useMotionValue,\n useScroll,\n useSpring,\n useTransform,\n useVelocity,\n} from \"motion/react\"\nimport type { MotionValue } from \"motion/react\"\n\nimport { cn } from \"@/lib/utils\"\n\ninterface ScrollVelocityRowProps extends React.HTMLAttributes {\n children: React.ReactNode\n baseVelocity?: number\n direction?: 1 | -1\n}\n\nexport const wrap = (min: number, max: number, v: number) => {\n const rangeSize = max - min\n return ((((v - min) % rangeSize) + rangeSize) % rangeSize) + min\n}\n\nconst ScrollVelocityContext = React.createContext | null>(\n null\n)\n\nexport function ScrollVelocityContainer({\n children,\n className,\n ...props\n}: React.HTMLAttributes) {\n const { scrollY } = useScroll()\n const scrollVelocity = useVelocity(scrollY)\n const smoothVelocity = useSpring(scrollVelocity, {\n damping: 50,\n stiffness: 400,\n })\n const velocityFactor = useTransform(smoothVelocity, (v) => {\n const sign = v < 0 ? -1 : 1\n const magnitude = Math.min(5, (Math.abs(v) / 1000) * 5)\n return sign * magnitude\n })\n\n return (\n \n
\n {children}\n
\n
\n )\n}\n\nexport function ScrollVelocityRow(props: ScrollVelocityRowProps) {\n const sharedVelocityFactor = useContext(ScrollVelocityContext)\n if (sharedVelocityFactor) {\n return (\n \n )\n }\n return \n}\n\ninterface ScrollVelocityRowImplProps extends ScrollVelocityRowProps {\n velocityFactor: MotionValue\n}\n\nfunction ScrollVelocityRowImpl({\n children,\n baseVelocity = 5,\n direction = 1,\n className,\n velocityFactor,\n ...props\n}: ScrollVelocityRowImplProps) {\n const containerRef = useRef(null)\n const blockRef = useRef(null)\n const [numCopies, setNumCopies] = useState(1)\n\n const baseX = useMotionValue(0)\n const baseDirectionRef = useRef(direction >= 0 ? 1 : -1)\n const currentDirectionRef = useRef(direction >= 0 ? 1 : -1)\n const unitWidth = useMotionValue(0)\n\n const isInViewRef = useRef(true)\n const isPageVisibleRef = useRef(true)\n const prefersReducedMotionRef = useRef(false)\n\n useEffect(() => {\n const container = containerRef.current\n const block = blockRef.current\n if (!container || !block) return\n\n const updateSizes = () => {\n const cw = container.offsetWidth || 0\n const bw = block.scrollWidth || 0\n unitWidth.set(bw)\n const nextCopies = bw > 0 ? Math.max(3, Math.ceil(cw / bw) + 2) : 1\n setNumCopies((prev) => (prev === nextCopies ? prev : nextCopies))\n }\n\n updateSizes()\n\n const ro = new ResizeObserver(updateSizes)\n ro.observe(container)\n ro.observe(block)\n\n const io = new IntersectionObserver(([entry]) => {\n isInViewRef.current = entry.isIntersecting\n })\n io.observe(container)\n\n const handleVisibility = () => {\n isPageVisibleRef.current = document.visibilityState === \"visible\"\n }\n document.addEventListener(\"visibilitychange\", handleVisibility, {\n passive: true,\n })\n handleVisibility()\n\n const mq = window.matchMedia(\"(prefers-reduced-motion: reduce)\")\n const handlePRM = () => {\n prefersReducedMotionRef.current = mq.matches\n }\n mq.addEventListener(\"change\", handlePRM)\n handlePRM()\n\n return () => {\n ro.disconnect()\n io.disconnect()\n document.removeEventListener(\"visibilitychange\", handleVisibility)\n mq.removeEventListener(\"change\", handlePRM)\n }\n }, [children, unitWidth])\n\n const x = useTransform([baseX, unitWidth], ([v, bw]) => {\n const width = Number(bw) || 1\n const offset = Number(v) || 0\n return `${-wrap(0, width, offset)}px`\n })\n\n useAnimationFrame((_, delta) => {\n if (!isInViewRef.current || !isPageVisibleRef.current) return\n const dt = delta / 1000\n const vf = velocityFactor.get()\n const absVf = Math.min(5, Math.abs(vf))\n const speedMultiplier = prefersReducedMotionRef.current ? 1 : 1 + absVf\n\n if (absVf > 0.1) {\n const scrollDirection = vf >= 0 ? 1 : -1\n currentDirectionRef.current = baseDirectionRef.current * scrollDirection\n }\n\n const bw = unitWidth.get() || 0\n if (bw <= 0) return\n const pixelsPerSecond = (bw * baseVelocity) / 100\n const moveBy =\n currentDirectionRef.current * pixelsPerSecond * speedMultiplier * dt\n baseX.set(baseX.get() + moveBy)\n })\n\n return (\n \n \n {Array.from({ length: numCopies }).map((_, i) => (\n \n {children}\n \n ))}\n \n \n )\n}\n\nfunction ScrollVelocityRowLocal(props: ScrollVelocityRowProps) {\n const { scrollY } = useScroll()\n const localVelocity = useVelocity(scrollY)\n const localSmoothVelocity = useSpring(localVelocity, {\n damping: 50,\n stiffness: 400,\n })\n const localVelocityFactor = useTransform(localSmoothVelocity, (v) => {\n const sign = v < 0 ? -1 : 1\n const magnitude = Math.min(5, (Math.abs(v) / 1000) * 5)\n return sign * magnitude\n })\n return (\n \n )\n}\n", + "content": "\"use client\"\n\nimport React, { useContext, useEffect, useRef, useState } from \"react\"\nimport {\n motion,\n useAnimationFrame,\n useMotionValue,\n useScroll,\n useSpring,\n useTransform,\n useVelocity,\n} from \"motion/react\"\nimport type { MotionValue } from \"motion/react\"\n\nimport { cn } from \"@/lib/utils\"\n\ninterface ScrollVelocityRowProps extends React.HTMLAttributes {\n children: React.ReactNode\n baseVelocity?: number\n direction?: 1 | -1\n scrollReactivity?: boolean\n}\n\nexport const wrap = (min: number, max: number, v: number) => {\n const rangeSize = max - min\n return ((((v - min) % rangeSize) + rangeSize) % rangeSize) + min\n}\n\nconst ScrollVelocityContext = React.createContext | null>(\n null\n)\n\nexport function ScrollVelocityContainer({\n children,\n className,\n ...props\n}: React.HTMLAttributes) {\n const { scrollY } = useScroll()\n const scrollVelocity = useVelocity(scrollY)\n const smoothVelocity = useSpring(scrollVelocity, {\n damping: 50,\n stiffness: 400,\n })\n const velocityFactor = useTransform(smoothVelocity, (v) => {\n const sign = v < 0 ? -1 : 1\n const magnitude = Math.min(5, (Math.abs(v) / 1000) * 5)\n return sign * magnitude\n })\n\n return (\n \n
\n {children}\n
\n
\n )\n}\n\nexport function ScrollVelocityRow(props: ScrollVelocityRowProps) {\n const sharedVelocityFactor = useContext(ScrollVelocityContext)\n if (sharedVelocityFactor) {\n return (\n \n )\n }\n return \n}\n\ninterface ScrollVelocityRowImplProps extends ScrollVelocityRowProps {\n velocityFactor: MotionValue\n}\n\nfunction ScrollVelocityRowImpl({\n children,\n baseVelocity = 5,\n direction = 1,\n className,\n velocityFactor,\n scrollReactivity = true,\n ...props\n}: ScrollVelocityRowImplProps) {\n const containerRef = useRef(null)\n const blockRef = useRef(null)\n const [numCopies, setNumCopies] = useState(1)\n\n const baseX = useMotionValue(0)\n const baseDirectionRef = useRef(direction >= 0 ? 1 : -1)\n const currentDirectionRef = useRef(direction >= 0 ? 1 : -1)\n const unitWidth = useMotionValue(0)\n\n const isInViewRef = useRef(true)\n const isPageVisibleRef = useRef(true)\n const prefersReducedMotionRef = useRef(false)\n\n useEffect(() => {\n const container = containerRef.current\n const block = blockRef.current\n if (!container || !block) return\n\n const updateSizes = () => {\n const cw = container.offsetWidth || 0\n const bw = block.scrollWidth || 0\n unitWidth.set(bw)\n const nextCopies = bw > 0 ? Math.max(3, Math.ceil(cw / bw) + 2) : 1\n setNumCopies((prev) => (prev === nextCopies ? prev : nextCopies))\n }\n\n updateSizes()\n\n const ro = new ResizeObserver(updateSizes)\n ro.observe(container)\n ro.observe(block)\n\n const io = new IntersectionObserver(([entry]) => {\n isInViewRef.current = entry.isIntersecting\n })\n io.observe(container)\n\n const handleVisibility = () => {\n isPageVisibleRef.current = document.visibilityState === \"visible\"\n }\n document.addEventListener(\"visibilitychange\", handleVisibility, {\n passive: true,\n })\n handleVisibility()\n\n const mq = window.matchMedia(\"(prefers-reduced-motion: reduce)\")\n const handlePRM = () => {\n prefersReducedMotionRef.current = mq.matches\n }\n mq.addEventListener(\"change\", handlePRM)\n handlePRM()\n\n return () => {\n ro.disconnect()\n io.disconnect()\n document.removeEventListener(\"visibilitychange\", handleVisibility)\n mq.removeEventListener(\"change\", handlePRM)\n }\n }, [children, unitWidth])\n\n const x = useTransform([baseX, unitWidth], ([v, bw]) => {\n const width = Number(bw) || 1\n const offset = Number(v) || 0\n return `${-wrap(0, width, offset)}px`\n })\n\n useAnimationFrame((_, delta) => {\n if (!isInViewRef.current || !isPageVisibleRef.current) return\n const dt = delta / 1000\n const vf = scrollReactivity ? velocityFactor.get() : 0\n const absVf = Math.min(5, Math.abs(vf))\n const speedMultiplier = prefersReducedMotionRef.current ? 1 : 1 + absVf\n\n if (absVf > 0.1) {\n const scrollDirection = vf >= 0 ? 1 : -1\n currentDirectionRef.current = baseDirectionRef.current * scrollDirection\n }\n\n const bw = unitWidth.get() || 0\n if (bw <= 0) return\n const pixelsPerSecond = (bw * baseVelocity) / 100\n const moveBy =\n currentDirectionRef.current * pixelsPerSecond * speedMultiplier * dt\n baseX.set(baseX.get() + moveBy)\n })\n\n return (\n \n \n {Array.from({ length: numCopies }).map((_, i) => (\n \n {children}\n \n ))}\n \n \n )\n}\n\nfunction ScrollVelocityRowLocal(props: ScrollVelocityRowProps) {\n const { scrollY } = useScroll()\n const localVelocity = useVelocity(scrollY)\n const localSmoothVelocity = useSpring(localVelocity, {\n damping: 50,\n stiffness: 400,\n })\n const localVelocityFactor = useTransform(localSmoothVelocity, (v) => {\n const sign = v < 0 ? -1 : 1\n const magnitude = Math.min(5, (Math.abs(v) / 1000) * 5)\n return sign * magnitude\n })\n return (\n \n )\n}\n", "type": "registry:ui" } ] diff --git a/apps/www/registry/magicui/scroll-based-velocity.tsx b/apps/www/registry/magicui/scroll-based-velocity.tsx index 7c67f9d9e..c45409240 100644 --- a/apps/www/registry/magicui/scroll-based-velocity.tsx +++ b/apps/www/registry/magicui/scroll-based-velocity.tsx @@ -18,6 +18,7 @@ interface ScrollVelocityRowProps extends React.HTMLAttributes { children: React.ReactNode baseVelocity?: number direction?: 1 | -1 + scrollReactivity?: boolean } export const wrap = (min: number, max: number, v: number) => { @@ -75,6 +76,7 @@ function ScrollVelocityRowImpl({ direction = 1, className, velocityFactor, + scrollReactivity = true, ...props }: ScrollVelocityRowImplProps) { const containerRef = useRef(null) @@ -146,7 +148,7 @@ function ScrollVelocityRowImpl({ useAnimationFrame((_, delta) => { if (!isInViewRef.current || !isPageVisibleRef.current) return const dt = delta / 1000 - const vf = velocityFactor.get() + const vf = scrollReactivity ? velocityFactor.get() : 0 const absVf = Math.min(5, Math.abs(vf)) const speedMultiplier = prefersReducedMotionRef.current ? 1 : 1 + absVf