From 4b47a6c2fa5f98583bc89eb548975bac6155e171 Mon Sep 17 00:00:00 2001 From: mohamed-younes16 Date: Wed, 4 Mar 2026 12:45:01 +0100 Subject: [PATCH] feat: support ReactNode in ScrollVelocity texts prop MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Change texts: string[] to texts: React.ReactNode[] in TS variants - Move   separator inside the span (encapsulated in scroller) - Replace numCopies! non-null assertion with numCopies ?? 6 - Update prop table description in demo to reflect ReactNode support - Applied consistently across all 4 variants (JS-CSS, JS-TW, TS-CSS, TS-TW) This is a non-breaking change — plain strings are valid ReactNode values, so all existing usage continues to work. The change unlocks rich content like icons, styled spans, gradients, and mixed JSX in the scrolling ticker. --- public/r/ScrollVelocity-JS-CSS.json | 2 +- public/r/ScrollVelocity-JS-TW.json | 2 +- public/r/ScrollVelocity-TS-CSS.json | 2 +- public/r/ScrollVelocity-TS-TW.json | 2 +- .../TextAnimations/ScrollVelocity/ScrollVelocity.jsx | 4 ++-- src/demo/TextAnimations/ScrollVelocityDemo.jsx | 4 ++-- .../TextAnimations/ScrollVelocity/ScrollVelocity.jsx | 4 ++-- .../TextAnimations/ScrollVelocity/ScrollVelocity.tsx | 10 +++++----- .../TextAnimations/ScrollVelocity/ScrollVelocity.tsx | 10 +++++----- 9 files changed, 20 insertions(+), 20 deletions(-) diff --git a/public/r/ScrollVelocity-JS-CSS.json b/public/r/ScrollVelocity-JS-CSS.json index 2b7047c0..925419de 100644 --- a/public/r/ScrollVelocity-JS-CSS.json +++ b/public/r/ScrollVelocity-JS-CSS.json @@ -13,7 +13,7 @@ { "type": "registry:component", "path": "ScrollVelocity/ScrollVelocity.jsx", - "content": "import { useRef, useLayoutEffect, useState } from 'react';\nimport {\n motion,\n useScroll,\n useSpring,\n useTransform,\n useMotionValue,\n useVelocity,\n useAnimationFrame\n} from 'motion/react';\nimport './ScrollVelocity.css';\n\nfunction useElementWidth(ref) {\n const [width, setWidth] = useState(0);\n\n useLayoutEffect(() => {\n function updateWidth() {\n if (ref.current) {\n setWidth(ref.current.offsetWidth);\n }\n }\n updateWidth();\n window.addEventListener('resize', updateWidth);\n return () => window.removeEventListener('resize', updateWidth);\n }, [ref]);\n\n return width;\n}\n\nexport const ScrollVelocity = ({\n scrollContainerRef,\n texts = [],\n velocity = 100,\n className = '',\n damping = 50,\n stiffness = 400,\n numCopies = 6,\n velocityMapping = { input: [0, 1000], output: [0, 5] },\n parallaxClassName = 'parallax',\n scrollerClassName = 'scroller',\n parallaxStyle,\n scrollerStyle\n}) => {\n function VelocityText({\n children,\n baseVelocity = velocity,\n scrollContainerRef,\n className = '',\n damping,\n stiffness,\n numCopies,\n velocityMapping,\n parallaxClassName,\n scrollerClassName,\n parallaxStyle,\n scrollerStyle\n }) {\n const baseX = useMotionValue(0);\n const scrollOptions = scrollContainerRef ? { container: scrollContainerRef } : {};\n const { scrollY } = useScroll(scrollOptions);\n const scrollVelocity = useVelocity(scrollY);\n const smoothVelocity = useSpring(scrollVelocity, {\n damping: damping ?? 50,\n stiffness: stiffness ?? 400\n });\n const velocityFactor = useTransform(\n smoothVelocity,\n velocityMapping?.input || [0, 1000],\n velocityMapping?.output || [0, 5],\n { clamp: false }\n );\n\n const copyRef = useRef(null);\n const copyWidth = useElementWidth(copyRef);\n\n function wrap(min, max, v) {\n const range = max - min;\n const mod = (((v - min) % range) + range) % range;\n return mod + min;\n }\n\n const x = useTransform(baseX, v => {\n if (copyWidth === 0) return '0px';\n return `${wrap(-copyWidth, 0, v)}px`;\n });\n\n const directionFactor = useRef(1);\n useAnimationFrame((t, delta) => {\n let moveBy = directionFactor.current * baseVelocity * (delta / 1000);\n\n if (velocityFactor.get() < 0) {\n directionFactor.current = -1;\n } else if (velocityFactor.get() > 0) {\n directionFactor.current = 1;\n }\n\n moveBy += directionFactor.current * moveBy * velocityFactor.get();\n baseX.set(baseX.get() + moveBy);\n });\n\n const spans = [];\n for (let i = 0; i < numCopies; i++) {\n spans.push(\n \n {children}\n \n );\n }\n\n return (\n
\n \n {spans}\n \n
\n );\n }\n\n return (\n
\n {texts.map((text, index) => (\n \n {text} \n \n ))}\n
\n );\n};\n\nexport default ScrollVelocity;\n" + "content": "import { useRef, useLayoutEffect, useState } from 'react';\nimport {\n motion,\n useScroll,\n useSpring,\n useTransform,\n useMotionValue,\n useVelocity,\n useAnimationFrame\n} from 'motion/react';\nimport './ScrollVelocity.css';\n\nfunction useElementWidth(ref) {\n const [width, setWidth] = useState(0);\n\n useLayoutEffect(() => {\n function updateWidth() {\n if (ref.current) {\n setWidth(ref.current.offsetWidth);\n }\n }\n updateWidth();\n window.addEventListener('resize', updateWidth);\n return () => window.removeEventListener('resize', updateWidth);\n }, [ref]);\n\n return width;\n}\n\nexport const ScrollVelocity = ({\n scrollContainerRef,\n texts = [],\n velocity = 100,\n className = '',\n damping = 50,\n stiffness = 400,\n numCopies = 6,\n velocityMapping = { input: [0, 1000], output: [0, 5] },\n parallaxClassName = 'parallax',\n scrollerClassName = 'scroller',\n parallaxStyle,\n scrollerStyle\n}) => {\n function VelocityText({\n children,\n baseVelocity = velocity,\n scrollContainerRef,\n className = '',\n damping,\n stiffness,\n numCopies,\n velocityMapping,\n parallaxClassName,\n scrollerClassName,\n parallaxStyle,\n scrollerStyle\n }) {\n const baseX = useMotionValue(0);\n const scrollOptions = scrollContainerRef ? { container: scrollContainerRef } : {};\n const { scrollY } = useScroll(scrollOptions);\n const scrollVelocity = useVelocity(scrollY);\n const smoothVelocity = useSpring(scrollVelocity, {\n damping: damping ?? 50,\n stiffness: stiffness ?? 400\n });\n const velocityFactor = useTransform(\n smoothVelocity,\n velocityMapping?.input || [0, 1000],\n velocityMapping?.output || [0, 5],\n { clamp: false }\n );\n\n const copyRef = useRef(null);\n const copyWidth = useElementWidth(copyRef);\n\n function wrap(min, max, v) {\n const range = max - min;\n const mod = (((v - min) % range) + range) % range;\n return mod + min;\n }\n\n const x = useTransform(baseX, v => {\n if (copyWidth === 0) return '0px';\n return `${wrap(-copyWidth, 0, v)}px`;\n });\n\n const directionFactor = useRef(1);\n useAnimationFrame((t, delta) => {\n let moveBy = directionFactor.current * baseVelocity * (delta / 1000);\n\n if (velocityFactor.get() < 0) {\n directionFactor.current = -1;\n } else if (velocityFactor.get() > 0) {\n directionFactor.current = 1;\n }\n\n moveBy += directionFactor.current * moveBy * velocityFactor.get();\n baseX.set(baseX.get() + moveBy);\n });\n\n const spans = [];\n for (let i = 0; i < numCopies; i++) {\n spans.push(\n \n {children} \n \n );\n }\n\n return (\n
\n \n {spans}\n \n
\n );\n }\n\n return (\n
\n {texts.map((text, index) => (\n \n {text}\n \n ))}\n
\n );\n};\n\nexport default ScrollVelocity;\n" } ], "registryDependencies": [], diff --git a/public/r/ScrollVelocity-JS-TW.json b/public/r/ScrollVelocity-JS-TW.json index d8fe0f5e..9c1b8216 100644 --- a/public/r/ScrollVelocity-JS-TW.json +++ b/public/r/ScrollVelocity-JS-TW.json @@ -8,7 +8,7 @@ { "type": "registry:component", "path": "ScrollVelocity/ScrollVelocity.jsx", - "content": "import { useRef, useLayoutEffect, useState } from 'react';\nimport {\n motion,\n useScroll,\n useSpring,\n useTransform,\n useMotionValue,\n useVelocity,\n useAnimationFrame\n} from 'motion/react';\n\nfunction useElementWidth(ref) {\n const [width, setWidth] = useState(0);\n\n useLayoutEffect(() => {\n function updateWidth() {\n if (ref.current) {\n setWidth(ref.current.offsetWidth);\n }\n }\n updateWidth();\n window.addEventListener('resize', updateWidth);\n return () => window.removeEventListener('resize', updateWidth);\n }, [ref]);\n\n return width;\n}\n\nexport const ScrollVelocity = ({\n scrollContainerRef,\n texts = [],\n velocity = 100,\n className = '',\n damping = 50,\n stiffness = 400,\n numCopies = 6,\n velocityMapping = { input: [0, 1000], output: [0, 5] },\n parallaxClassName,\n scrollerClassName,\n parallaxStyle,\n scrollerStyle\n}) => {\n function VelocityText({\n children,\n baseVelocity = velocity,\n scrollContainerRef,\n className = '',\n damping,\n stiffness,\n numCopies,\n velocityMapping,\n parallaxClassName,\n scrollerClassName,\n parallaxStyle,\n scrollerStyle\n }) {\n const baseX = useMotionValue(0);\n const scrollOptions = scrollContainerRef ? { container: scrollContainerRef } : {};\n const { scrollY } = useScroll(scrollOptions);\n const scrollVelocity = useVelocity(scrollY);\n const smoothVelocity = useSpring(scrollVelocity, {\n damping: damping ?? 50,\n stiffness: stiffness ?? 400\n });\n const velocityFactor = useTransform(\n smoothVelocity,\n velocityMapping?.input || [0, 1000],\n velocityMapping?.output || [0, 5],\n { clamp: false }\n );\n\n const copyRef = useRef(null);\n const copyWidth = useElementWidth(copyRef);\n\n function wrap(min, max, v) {\n const range = max - min;\n const mod = (((v - min) % range) + range) % range;\n return mod + min;\n }\n\n const x = useTransform(baseX, v => {\n if (copyWidth === 0) return '0px';\n return `${wrap(-copyWidth, 0, v)}px`;\n });\n\n const directionFactor = useRef(1);\n useAnimationFrame((t, delta) => {\n let moveBy = directionFactor.current * baseVelocity * (delta / 1000);\n\n if (velocityFactor.get() < 0) {\n directionFactor.current = -1;\n } else if (velocityFactor.get() > 0) {\n directionFactor.current = 1;\n }\n\n moveBy += directionFactor.current * moveBy * velocityFactor.get();\n baseX.set(baseX.get() + moveBy);\n });\n\n const spans = [];\n for (let i = 0; i < (numCopies ?? 1); i++) {\n spans.push(\n \n {children}\n \n );\n }\n\n return (\n
\n \n {spans}\n \n
\n );\n }\n\n return (\n
\n {texts.map((text, index) => (\n \n {text} \n \n ))}\n
\n );\n};\n\nexport default ScrollVelocity;\n" + "content": "import { useRef, useLayoutEffect, useState } from 'react';\nimport {\n motion,\n useScroll,\n useSpring,\n useTransform,\n useMotionValue,\n useVelocity,\n useAnimationFrame\n} from 'motion/react';\n\nfunction useElementWidth(ref) {\n const [width, setWidth] = useState(0);\n\n useLayoutEffect(() => {\n function updateWidth() {\n if (ref.current) {\n setWidth(ref.current.offsetWidth);\n }\n }\n updateWidth();\n window.addEventListener('resize', updateWidth);\n return () => window.removeEventListener('resize', updateWidth);\n }, [ref]);\n\n return width;\n}\n\nexport const ScrollVelocity = ({\n scrollContainerRef,\n texts = [],\n velocity = 100,\n className = '',\n damping = 50,\n stiffness = 400,\n numCopies = 6,\n velocityMapping = { input: [0, 1000], output: [0, 5] },\n parallaxClassName,\n scrollerClassName,\n parallaxStyle,\n scrollerStyle\n}) => {\n function VelocityText({\n children,\n baseVelocity = velocity,\n scrollContainerRef,\n className = '',\n damping,\n stiffness,\n numCopies,\n velocityMapping,\n parallaxClassName,\n scrollerClassName,\n parallaxStyle,\n scrollerStyle\n }) {\n const baseX = useMotionValue(0);\n const scrollOptions = scrollContainerRef ? { container: scrollContainerRef } : {};\n const { scrollY } = useScroll(scrollOptions);\n const scrollVelocity = useVelocity(scrollY);\n const smoothVelocity = useSpring(scrollVelocity, {\n damping: damping ?? 50,\n stiffness: stiffness ?? 400\n });\n const velocityFactor = useTransform(\n smoothVelocity,\n velocityMapping?.input || [0, 1000],\n velocityMapping?.output || [0, 5],\n { clamp: false }\n );\n\n const copyRef = useRef(null);\n const copyWidth = useElementWidth(copyRef);\n\n function wrap(min, max, v) {\n const range = max - min;\n const mod = (((v - min) % range) + range) % range;\n return mod + min;\n }\n\n const x = useTransform(baseX, v => {\n if (copyWidth === 0) return '0px';\n return `${wrap(-copyWidth, 0, v)}px`;\n });\n\n const directionFactor = useRef(1);\n useAnimationFrame((t, delta) => {\n let moveBy = directionFactor.current * baseVelocity * (delta / 1000);\n\n if (velocityFactor.get() < 0) {\n directionFactor.current = -1;\n } else if (velocityFactor.get() > 0) {\n directionFactor.current = 1;\n }\n\n moveBy += directionFactor.current * moveBy * velocityFactor.get();\n baseX.set(baseX.get() + moveBy);\n });\n\n const spans = [];\n for (let i = 0; i < (numCopies ?? 1); i++) {\n spans.push(\n \n {children} \n \n );\n }\n\n return (\n
\n \n {spans}\n \n
\n );\n }\n\n return (\n
\n {texts.map((text, index) => (\n \n {text}\n \n ))}\n
\n );\n};\n\nexport default ScrollVelocity;\n" } ], "registryDependencies": [], diff --git a/public/r/ScrollVelocity-TS-CSS.json b/public/r/ScrollVelocity-TS-CSS.json index 94b532bb..d7400f6b 100644 --- a/public/r/ScrollVelocity-TS-CSS.json +++ b/public/r/ScrollVelocity-TS-CSS.json @@ -13,7 +13,7 @@ { "type": "registry:component", "path": "ScrollVelocity/ScrollVelocity.tsx", - "content": "import React, { useRef, useLayoutEffect, useState } from 'react';\nimport {\n motion,\n useScroll,\n useSpring,\n useTransform,\n useMotionValue,\n useVelocity,\n useAnimationFrame\n} from 'motion/react';\nimport './ScrollVelocity.css';\n\ninterface VelocityMapping {\n input: [number, number];\n output: [number, number];\n}\n\ninterface VelocityTextProps {\n children: React.ReactNode;\n baseVelocity: number;\n scrollContainerRef?: React.RefObject;\n className?: string;\n damping?: number;\n stiffness?: number;\n numCopies?: number;\n velocityMapping?: VelocityMapping;\n parallaxClassName?: string;\n scrollerClassName?: string;\n parallaxStyle?: React.CSSProperties;\n scrollerStyle?: React.CSSProperties;\n}\n\ninterface ScrollVelocityProps {\n scrollContainerRef?: React.RefObject;\n texts: string[];\n velocity?: number;\n className?: string;\n damping?: number;\n stiffness?: number;\n numCopies?: number;\n velocityMapping?: VelocityMapping;\n parallaxClassName?: string;\n scrollerClassName?: string;\n parallaxStyle?: React.CSSProperties;\n scrollerStyle?: React.CSSProperties;\n}\n\nfunction useElementWidth(ref: React.RefObject): number {\n const [width, setWidth] = useState(0);\n\n useLayoutEffect(() => {\n function updateWidth() {\n if (ref.current) {\n setWidth(ref.current.offsetWidth);\n }\n }\n updateWidth();\n window.addEventListener('resize', updateWidth);\n return () => window.removeEventListener('resize', updateWidth);\n }, [ref]);\n\n return width;\n}\n\nexport const ScrollVelocity: React.FC = ({\n scrollContainerRef,\n texts = [],\n velocity = 100,\n className = '',\n damping = 50,\n stiffness = 400,\n numCopies = 6,\n velocityMapping = { input: [0, 1000], output: [0, 5] },\n parallaxClassName = 'parallax',\n scrollerClassName = 'scroller',\n parallaxStyle,\n scrollerStyle\n}) => {\n function VelocityText({\n children,\n baseVelocity = velocity,\n scrollContainerRef,\n className = '',\n damping,\n stiffness,\n numCopies,\n velocityMapping,\n parallaxClassName,\n scrollerClassName,\n parallaxStyle,\n scrollerStyle\n }: VelocityTextProps) {\n const baseX = useMotionValue(0);\n const scrollOptions = scrollContainerRef ? { container: scrollContainerRef } : {};\n const { scrollY } = useScroll(scrollOptions);\n const scrollVelocity = useVelocity(scrollY);\n const smoothVelocity = useSpring(scrollVelocity, {\n damping: damping ?? 50,\n stiffness: stiffness ?? 400\n });\n const velocityFactor = useTransform(\n smoothVelocity,\n velocityMapping?.input || [0, 1000],\n velocityMapping?.output || [0, 5],\n { clamp: false }\n );\n\n const copyRef = useRef(null);\n const copyWidth = useElementWidth(copyRef);\n\n function wrap(min: number, max: number, v: number): number {\n const range = max - min;\n const mod = (((v - min) % range) + range) % range;\n return mod + min;\n }\n\n const x = useTransform(baseX, v => {\n if (copyWidth === 0) return '0px';\n return `${wrap(-copyWidth, 0, v)}px`;\n });\n\n const directionFactor = useRef(1);\n useAnimationFrame((t, delta) => {\n let moveBy = directionFactor.current * baseVelocity * (delta / 1000);\n\n if (velocityFactor.get() < 0) {\n directionFactor.current = -1;\n } else if (velocityFactor.get() > 0) {\n directionFactor.current = 1;\n }\n\n moveBy += directionFactor.current * moveBy * velocityFactor.get();\n baseX.set(baseX.get() + moveBy);\n });\n\n const spans = [];\n for (let i = 0; i < numCopies!; i++) {\n spans.push(\n \n {children}\n \n );\n }\n\n return (\n
\n \n {spans}\n \n
\n );\n }\n\n return (\n
\n {texts.map((text: string, index: number) => (\n \n {text} \n \n ))}\n
\n );\n};\n\nexport default ScrollVelocity;\n" + "content": "import React, { useRef, useLayoutEffect, useState } from 'react';\nimport {\n motion,\n useScroll,\n useSpring,\n useTransform,\n useMotionValue,\n useVelocity,\n useAnimationFrame\n} from 'motion/react';\nimport './ScrollVelocity.css';\n\ninterface VelocityMapping {\n input: [number, number];\n output: [number, number];\n}\n\ninterface VelocityTextProps {\n children: React.ReactNode;\n baseVelocity: number;\n scrollContainerRef?: React.RefObject;\n className?: string;\n damping?: number;\n stiffness?: number;\n numCopies?: number;\n velocityMapping?: VelocityMapping;\n parallaxClassName?: string;\n scrollerClassName?: string;\n parallaxStyle?: React.CSSProperties;\n scrollerStyle?: React.CSSProperties;\n}\n\ninterface ScrollVelocityProps {\n scrollContainerRef?: React.RefObject;\n texts: React.ReactNode[];\n velocity?: number;\n className?: string;\n damping?: number;\n stiffness?: number;\n numCopies?: number;\n velocityMapping?: VelocityMapping;\n parallaxClassName?: string;\n scrollerClassName?: string;\n parallaxStyle?: React.CSSProperties;\n scrollerStyle?: React.CSSProperties;\n}\n\nfunction useElementWidth(ref: React.RefObject): number {\n const [width, setWidth] = useState(0);\n\n useLayoutEffect(() => {\n function updateWidth() {\n if (ref.current) {\n setWidth(ref.current.offsetWidth);\n }\n }\n updateWidth();\n window.addEventListener('resize', updateWidth);\n return () => window.removeEventListener('resize', updateWidth);\n }, [ref]);\n\n return width;\n}\n\nexport const ScrollVelocity: React.FC = ({\n scrollContainerRef,\n texts = [],\n velocity = 100,\n className = '',\n damping = 50,\n stiffness = 400,\n numCopies = 6,\n velocityMapping = { input: [0, 1000], output: [0, 5] },\n parallaxClassName = 'parallax',\n scrollerClassName = 'scroller',\n parallaxStyle,\n scrollerStyle\n}) => {\n function VelocityText({\n children,\n baseVelocity = velocity,\n scrollContainerRef,\n className = '',\n damping,\n stiffness,\n numCopies,\n velocityMapping,\n parallaxClassName,\n scrollerClassName,\n parallaxStyle,\n scrollerStyle\n }: VelocityTextProps) {\n const baseX = useMotionValue(0);\n const scrollOptions = scrollContainerRef ? { container: scrollContainerRef } : {};\n const { scrollY } = useScroll(scrollOptions);\n const scrollVelocity = useVelocity(scrollY);\n const smoothVelocity = useSpring(scrollVelocity, {\n damping: damping ?? 50,\n stiffness: stiffness ?? 400\n });\n const velocityFactor = useTransform(\n smoothVelocity,\n velocityMapping?.input || [0, 1000],\n velocityMapping?.output || [0, 5],\n { clamp: false }\n );\n\n const copyRef = useRef(null);\n const copyWidth = useElementWidth(copyRef);\n\n function wrap(min: number, max: number, v: number): number {\n const range = max - min;\n const mod = (((v - min) % range) + range) % range;\n return mod + min;\n }\n\n const x = useTransform(baseX, v => {\n if (copyWidth === 0) return '0px';\n return `${wrap(-copyWidth, 0, v)}px`;\n });\n\n const directionFactor = useRef(1);\n useAnimationFrame((t, delta) => {\n let moveBy = directionFactor.current * baseVelocity * (delta / 1000);\n\n if (velocityFactor.get() < 0) {\n directionFactor.current = -1;\n } else if (velocityFactor.get() > 0) {\n directionFactor.current = 1;\n }\n\n moveBy += directionFactor.current * moveBy * velocityFactor.get();\n baseX.set(baseX.get() + moveBy);\n });\n\n const spans = [];\n for (let i = 0; i < (numCopies ?? 6); i++) {\n spans.push(\n \n {children} \n \n );\n }\n\n return (\n
\n \n {spans}\n \n
\n );\n }\n\n return (\n
\n {texts.map((text, index) => (\n \n {text}\n \n ))}\n
\n );\n};\n\nexport default ScrollVelocity;\n" } ], "registryDependencies": [], diff --git a/public/r/ScrollVelocity-TS-TW.json b/public/r/ScrollVelocity-TS-TW.json index 8cb66b96..e3872326 100644 --- a/public/r/ScrollVelocity-TS-TW.json +++ b/public/r/ScrollVelocity-TS-TW.json @@ -8,7 +8,7 @@ { "type": "registry:component", "path": "ScrollVelocity/ScrollVelocity.tsx", - "content": "import React, { useRef, useLayoutEffect, useState } from 'react';\nimport {\n motion,\n useScroll,\n useSpring,\n useTransform,\n useMotionValue,\n useVelocity,\n useAnimationFrame\n} from 'motion/react';\n\ninterface VelocityMapping {\n input: [number, number];\n output: [number, number];\n}\n\ninterface VelocityTextProps {\n children: React.ReactNode;\n baseVelocity: number;\n scrollContainerRef?: React.RefObject;\n className?: string;\n damping?: number;\n stiffness?: number;\n numCopies?: number;\n velocityMapping?: VelocityMapping;\n parallaxClassName?: string;\n scrollerClassName?: string;\n parallaxStyle?: React.CSSProperties;\n scrollerStyle?: React.CSSProperties;\n}\n\ninterface ScrollVelocityProps {\n scrollContainerRef?: React.RefObject;\n texts: string[];\n velocity?: number;\n className?: string;\n damping?: number;\n stiffness?: number;\n numCopies?: number;\n velocityMapping?: VelocityMapping;\n parallaxClassName?: string;\n scrollerClassName?: string;\n parallaxStyle?: React.CSSProperties;\n scrollerStyle?: React.CSSProperties;\n}\n\nfunction useElementWidth(ref: React.RefObject): number {\n const [width, setWidth] = useState(0);\n\n useLayoutEffect(() => {\n function updateWidth() {\n if (ref.current) {\n setWidth(ref.current.offsetWidth);\n }\n }\n updateWidth();\n window.addEventListener('resize', updateWidth);\n return () => window.removeEventListener('resize', updateWidth);\n }, [ref]);\n\n return width;\n}\n\nexport const ScrollVelocity: React.FC = ({\n scrollContainerRef,\n texts = [],\n velocity = 100,\n className = '',\n damping = 50,\n stiffness = 400,\n numCopies = 6,\n velocityMapping = { input: [0, 1000], output: [0, 5] },\n parallaxClassName,\n scrollerClassName,\n parallaxStyle,\n scrollerStyle\n}) => {\n function VelocityText({\n children,\n baseVelocity = velocity,\n scrollContainerRef,\n className = '',\n damping,\n stiffness,\n numCopies,\n velocityMapping,\n parallaxClassName,\n scrollerClassName,\n parallaxStyle,\n scrollerStyle\n }: VelocityTextProps) {\n const baseX = useMotionValue(0);\n const scrollOptions = scrollContainerRef ? { container: scrollContainerRef } : {};\n const { scrollY } = useScroll(scrollOptions);\n const scrollVelocity = useVelocity(scrollY);\n const smoothVelocity = useSpring(scrollVelocity, {\n damping: damping ?? 50,\n stiffness: stiffness ?? 400\n });\n const velocityFactor = useTransform(\n smoothVelocity,\n velocityMapping?.input || [0, 1000],\n velocityMapping?.output || [0, 5],\n { clamp: false }\n );\n\n const copyRef = useRef(null);\n const copyWidth = useElementWidth(copyRef);\n\n function wrap(min: number, max: number, v: number): number {\n const range = max - min;\n const mod = (((v - min) % range) + range) % range;\n return mod + min;\n }\n\n const x = useTransform(baseX, v => {\n if (copyWidth === 0) return '0px';\n return `${wrap(-copyWidth, 0, v)}px`;\n });\n\n const directionFactor = useRef(1);\n useAnimationFrame((t, delta) => {\n let moveBy = directionFactor.current * baseVelocity * (delta / 1000);\n\n if (velocityFactor.get() < 0) {\n directionFactor.current = -1;\n } else if (velocityFactor.get() > 0) {\n directionFactor.current = 1;\n }\n\n moveBy += directionFactor.current * moveBy * velocityFactor.get();\n baseX.set(baseX.get() + moveBy);\n });\n\n const spans = [];\n for (let i = 0; i < numCopies!; i++) {\n spans.push(\n \n {children}\n \n );\n }\n\n return (\n
\n \n {spans}\n \n
\n );\n }\n\n return (\n
\n {texts.map((text: string, index: number) => (\n \n {text} \n \n ))}\n
\n );\n};\n\nexport default ScrollVelocity;\n" + "content": "import React, { useRef, useLayoutEffect, useState } from 'react';\nimport {\n motion,\n useScroll,\n useSpring,\n useTransform,\n useMotionValue,\n useVelocity,\n useAnimationFrame\n} from 'motion/react';\n\ninterface VelocityMapping {\n input: [number, number];\n output: [number, number];\n}\n\ninterface VelocityTextProps {\n children: React.ReactNode;\n baseVelocity: number;\n scrollContainerRef?: React.RefObject;\n className?: string;\n damping?: number;\n stiffness?: number;\n numCopies?: number;\n velocityMapping?: VelocityMapping;\n parallaxClassName?: string;\n scrollerClassName?: string;\n parallaxStyle?: React.CSSProperties;\n scrollerStyle?: React.CSSProperties;\n}\n\ninterface ScrollVelocityProps {\n scrollContainerRef?: React.RefObject;\n texts: React.ReactNode[];\n velocity?: number;\n className?: string;\n damping?: number;\n stiffness?: number;\n numCopies?: number;\n velocityMapping?: VelocityMapping;\n parallaxClassName?: string;\n scrollerClassName?: string;\n parallaxStyle?: React.CSSProperties;\n scrollerStyle?: React.CSSProperties;\n}\n\nfunction useElementWidth(ref: React.RefObject): number {\n const [width, setWidth] = useState(0);\n\n useLayoutEffect(() => {\n function updateWidth() {\n if (ref.current) {\n setWidth(ref.current.offsetWidth);\n }\n }\n updateWidth();\n window.addEventListener('resize', updateWidth);\n return () => window.removeEventListener('resize', updateWidth);\n }, [ref]);\n\n return width;\n}\n\nexport const ScrollVelocity: React.FC = ({\n scrollContainerRef,\n texts = [],\n velocity = 100,\n className = '',\n damping = 50,\n stiffness = 400,\n numCopies = 6,\n velocityMapping = { input: [0, 1000], output: [0, 5] },\n parallaxClassName,\n scrollerClassName,\n parallaxStyle,\n scrollerStyle\n}) => {\n function VelocityText({\n children,\n baseVelocity = velocity,\n scrollContainerRef,\n className = '',\n damping,\n stiffness,\n numCopies,\n velocityMapping,\n parallaxClassName,\n scrollerClassName,\n parallaxStyle,\n scrollerStyle\n }: VelocityTextProps) {\n const baseX = useMotionValue(0);\n const scrollOptions = scrollContainerRef ? { container: scrollContainerRef } : {};\n const { scrollY } = useScroll(scrollOptions);\n const scrollVelocity = useVelocity(scrollY);\n const smoothVelocity = useSpring(scrollVelocity, {\n damping: damping ?? 50,\n stiffness: stiffness ?? 400\n });\n const velocityFactor = useTransform(\n smoothVelocity,\n velocityMapping?.input || [0, 1000],\n velocityMapping?.output || [0, 5],\n { clamp: false }\n );\n\n const copyRef = useRef(null);\n const copyWidth = useElementWidth(copyRef);\n\n function wrap(min: number, max: number, v: number): number {\n const range = max - min;\n const mod = (((v - min) % range) + range) % range;\n return mod + min;\n }\n\n const x = useTransform(baseX, v => {\n if (copyWidth === 0) return '0px';\n return `${wrap(-copyWidth, 0, v)}px`;\n });\n\n const directionFactor = useRef(1);\n useAnimationFrame((t, delta) => {\n let moveBy = directionFactor.current * baseVelocity * (delta / 1000);\n\n if (velocityFactor.get() < 0) {\n directionFactor.current = -1;\n } else if (velocityFactor.get() > 0) {\n directionFactor.current = 1;\n }\n\n moveBy += directionFactor.current * moveBy * velocityFactor.get();\n baseX.set(baseX.get() + moveBy);\n });\n\n const spans = [];\n for (let i = 0; i < (numCopies ?? 6); i++) {\n spans.push(\n \n {children} \n \n );\n }\n\n return (\n
\n \n {spans}\n \n
\n );\n }\n\n return (\n
\n {texts.map((text, index) => (\n \n {text}\n \n ))}\n
\n );\n};\n\nexport default ScrollVelocity;\n" } ], "registryDependencies": [], diff --git a/src/content/TextAnimations/ScrollVelocity/ScrollVelocity.jsx b/src/content/TextAnimations/ScrollVelocity/ScrollVelocity.jsx index 981e50bb..bdf4a6ae 100644 --- a/src/content/TextAnimations/ScrollVelocity/ScrollVelocity.jsx +++ b/src/content/TextAnimations/ScrollVelocity/ScrollVelocity.jsx @@ -102,7 +102,7 @@ export const ScrollVelocity = ({ for (let i = 0; i < numCopies; i++) { spans.push( - {children} + {children}  ); } @@ -133,7 +133,7 @@ export const ScrollVelocity = ({ parallaxStyle={parallaxStyle} scrollerStyle={scrollerStyle} > - {text}  + {text} ))} diff --git a/src/demo/TextAnimations/ScrollVelocityDemo.jsx b/src/demo/TextAnimations/ScrollVelocityDemo.jsx index 6baa707a..8167e156 100644 --- a/src/demo/TextAnimations/ScrollVelocityDemo.jsx +++ b/src/demo/TextAnimations/ScrollVelocityDemo.jsx @@ -33,9 +33,9 @@ const ScrollVelocityDemo = () => { }, { name: 'texts', - type: 'string[]', + type: 'React.ReactNode[]', default: '[]', - description: 'Array of strings to display as scrolling text.' + description: 'Array of items to display as scrolling content. Accepts strings, JSX elements, icons, or any valid React node.' }, { name: 'velocity', diff --git a/src/tailwind/TextAnimations/ScrollVelocity/ScrollVelocity.jsx b/src/tailwind/TextAnimations/ScrollVelocity/ScrollVelocity.jsx index 1b57c04c..3e206f9f 100644 --- a/src/tailwind/TextAnimations/ScrollVelocity/ScrollVelocity.jsx +++ b/src/tailwind/TextAnimations/ScrollVelocity/ScrollVelocity.jsx @@ -101,7 +101,7 @@ export const ScrollVelocity = ({ for (let i = 0; i < (numCopies ?? 1); i++) { spans.push( - {children} + {children}  ); } @@ -135,7 +135,7 @@ export const ScrollVelocity = ({ parallaxStyle={parallaxStyle} scrollerStyle={scrollerStyle} > - {text}  + {text} ))} diff --git a/src/ts-default/TextAnimations/ScrollVelocity/ScrollVelocity.tsx b/src/ts-default/TextAnimations/ScrollVelocity/ScrollVelocity.tsx index 5465420e..ac1b5421 100644 --- a/src/ts-default/TextAnimations/ScrollVelocity/ScrollVelocity.tsx +++ b/src/ts-default/TextAnimations/ScrollVelocity/ScrollVelocity.tsx @@ -32,7 +32,7 @@ interface VelocityTextProps { interface ScrollVelocityProps { scrollContainerRef?: React.RefObject; - texts: string[]; + texts: React.ReactNode[]; velocity?: number; className?: string; damping?: number; @@ -134,10 +134,10 @@ export const ScrollVelocity: React.FC = ({ }); const spans = []; - for (let i = 0; i < numCopies!; i++) { + for (let i = 0; i < (numCopies ?? 6); i++) { spans.push( - {children} + {children}  ); } @@ -153,7 +153,7 @@ export const ScrollVelocity: React.FC = ({ return (
- {texts.map((text: string, index: number) => ( + {texts.map((text, index) => ( = ({ parallaxStyle={parallaxStyle} scrollerStyle={scrollerStyle} > - {text}  + {text} ))}
diff --git a/src/ts-tailwind/TextAnimations/ScrollVelocity/ScrollVelocity.tsx b/src/ts-tailwind/TextAnimations/ScrollVelocity/ScrollVelocity.tsx index 3b37deae..0996e043 100644 --- a/src/ts-tailwind/TextAnimations/ScrollVelocity/ScrollVelocity.tsx +++ b/src/ts-tailwind/TextAnimations/ScrollVelocity/ScrollVelocity.tsx @@ -31,7 +31,7 @@ interface VelocityTextProps { interface ScrollVelocityProps { scrollContainerRef?: React.RefObject; - texts: string[]; + texts: React.ReactNode[]; velocity?: number; className?: string; damping?: number; @@ -133,10 +133,10 @@ export const ScrollVelocity: React.FC = ({ }); const spans = []; - for (let i = 0; i < numCopies!; i++) { + for (let i = 0; i < (numCopies ?? 6); i++) { spans.push( - {children} + {children}  ); } @@ -155,7 +155,7 @@ export const ScrollVelocity: React.FC = ({ return (
- {texts.map((text: string, index: number) => ( + {texts.map((text, index) => ( = ({ parallaxStyle={parallaxStyle} scrollerStyle={scrollerStyle} > - {text}  + {text} ))}