[ui] · motion
ScrollBasedVelocity
Marquee infinito cuya velocidad y dirección reaccionan al scroll del usuario.
motion
@/components/ui/scroll-based-velocityScrollBasedVelocity
SFESS UI✦
SFESS UI✦
1"use client";2import React, { useContext, useEffect, useRef, useState } from "react"3import {4 motion,5 useAnimationFrame,6 useMotionValue,7 useScroll,8 useSpring,9 useTransform,10 useVelocity,11} from "motion/react"12 13import { cn } from "@/lib/utils"14 15export const wrap = (min, max, v) => {16 const rangeSize = max - min17 return ((((v - min) % rangeSize) + rangeSize) % rangeSize) + min18}19 20const ScrollVelocityContext = React.createContext(null)21 22export function ScrollVelocityContainer({23 children,24 className,25 ...props26}) {27 const { scrollY } = useScroll()28 const scrollVelocity = useVelocity(scrollY)29 const smoothVelocity = useSpring(scrollVelocity, {30 damping: 50,31 stiffness: 400,32 })33 const velocityFactor = useTransform(smoothVelocity, (v) => {34 const sign = v < 0 ? -1 : 135 const magnitude = Math.min(5, (Math.abs(v) / 1000) * 5)36 return sign * magnitude37 })38 39 return (40 <ScrollVelocityContext.Provider value={velocityFactor}>41 <div className={cn("relative w-full", className)} {...props}>42 {children}43 </div>44 </ScrollVelocityContext.Provider>45 );46}47 48export function ScrollVelocityRow(props) {49 const sharedVelocityFactor = useContext(ScrollVelocityContext)50 if (sharedVelocityFactor) {51 return (<ScrollVelocityRowImpl {...props} velocityFactor={sharedVelocityFactor} />);52 }53 return <ScrollVelocityRowLocal {...props} />;54}55 56function ScrollVelocityRowImpl({57 children,58 baseVelocity = 5,59 direction = 1,60 className,61 velocityFactor,62 scrollReactivity = true,63 ...props64}) {65 const containerRef = useRef(null)66 const blockRef = useRef(null)67 const [numCopies, setNumCopies] = useState(1)68 69 const baseX = useMotionValue(0)70 const baseDirectionRef = useRef(direction >= 0 ? 1 : -1)71 const currentDirectionRef = useRef(direction >= 0 ? 1 : -1)72 const unitWidth = useMotionValue(0)73 74 const isInViewRef = useRef(true)75 const isPageVisibleRef = useRef(true)76 const prefersReducedMotionRef = useRef(false)77 78 useEffect(() => {79 const container = containerRef.current80 const block = blockRef.current81 let ro = null82 let io = null83 let mq = null84 const handleVisibility = () => {85 isPageVisibleRef.current = document.visibilityState === "visible"86 }87 const handlePRM = () => {88 if (mq) {89 prefersReducedMotionRef.current = mq.matches90 }91 }92 93 if (container && block) {94 const updateSizes = () => {95 const cw = container.offsetWidth || 096 const bw = block.scrollWidth || 097 unitWidth.set(bw)98 const nextCopies = bw > 0 ? Math.max(3, Math.ceil(cw / bw) + 2) : 199 setNumCopies((prev) => (prev === nextCopies ? prev : nextCopies))100 }101 102 updateSizes()103 104 ro = new ResizeObserver(updateSizes)105 ro.observe(container)106 ro.observe(block)107 108 io = new IntersectionObserver(([entry]) => {109 isInViewRef.current = entry.isIntersecting110 })111 io.observe(container)112 113 document.addEventListener("visibilitychange", handleVisibility, {114 passive: true,115 })116 handleVisibility()117 118 mq = window.matchMedia("(prefers-reduced-motion: reduce)")119 mq.addEventListener("change", handlePRM)120 handlePRM()121 }122 123 return () => {124 if (ro) {125 ro.disconnect()126 }127 if (io) {128 io.disconnect()129 }130 document.removeEventListener("visibilitychange", handleVisibility)131 if (mq) {132 mq.removeEventListener("change", handlePRM)133 }134 };135 }, [children, unitWidth])136 137 const x = useTransform([baseX, unitWidth], ([v, bw]) => {138 const width = Number(bw) || 1139 const offset = Number(v) || 0140 return `${-wrap(0, width, offset)}px`;141 })142 143 useAnimationFrame((_, delta) => {144 if (!isInViewRef.current || !isPageVisibleRef.current) return145 const dt = delta / 1000146 const vf = scrollReactivity ? velocityFactor.get() : 0147 const absVf = Math.min(5, Math.abs(vf))148 const speedMultiplier = prefersReducedMotionRef.current ? 1 : 1 + absVf149 150 if (absVf > 0.1) {151 const scrollDirection = vf >= 0 ? 1 : -1152 currentDirectionRef.current = baseDirectionRef.current * scrollDirection153 }154 155 const bw = unitWidth.get() || 0156 if (bw <= 0) return157 const pixelsPerSecond = (bw * baseVelocity) / 100158 const moveBy =159 currentDirectionRef.current * pixelsPerSecond * speedMultiplier * dt160 baseX.set(baseX.get() + moveBy)161 })162 163 return (164 <div165 ref={containerRef}166 className={cn("w-full overflow-hidden whitespace-nowrap", className)}167 {...props}>168 <motion.div169 className="inline-flex transform-gpu items-center will-change-transform select-none"170 style={{ x }}>171 {Array.from({ length: numCopies }).map((_, i) => (172 <div173 key={i}174 ref={i === 0 ? blockRef : null}175 aria-hidden={i !== 0}176 className="inline-flex shrink-0 items-center">177 {children}178 </div>179 ))}180 </motion.div>181 </div>182 );183}184 185function ScrollVelocityRowLocal(props) {186 const { scrollY } = useScroll()187 const localVelocity = useVelocity(scrollY)188 const localSmoothVelocity = useSpring(localVelocity, {189 damping: 50,190 stiffness: 400,191 })192 const localVelocityFactor = useTransform(localSmoothVelocity, (v) => {193 const sign = v < 0 ? -1 : 1194 const magnitude = Math.min(5, (Math.abs(v) / 1000) * 5)195 return sign * magnitude196 })197 return (<ScrollVelocityRowImpl {...props} velocityFactor={localVelocityFactor} />);198}199 Uso
Props
PropTipoDefaultDescripción
baseVelocitynumber5Velocidad base (% del ancho/s).
direction1 | -11Dirección inicial.
scrollReactivitybooleantrueReacciona a la velocidad del scroll.
Acciones del componente
Search for a command to run...
Potenciá tu negocio con SFESS
Software a medida, IA integrada y ciberseguridad avanzada para empresas.
0años de ingeniería
0proyectos entregados
0% uptime
Servicios destacados
Consultoría SEO Avanzado & Analítica
Auditoría técnica profunda para posicionar tu negocio en los primeros resultados de Google.
SEO TécnicoGA4Páginas Web Corporativas Complejas
Plataformas empresariales masivas con múltiples secciones y animaciones fluidas.
Framer MotionMulti-pageIntegración de Inteligencia Artificial en Negocios
Automatizamos la atención, las reservas y las ventas de tu negocio con agentes de IA.
AI AgentsChatbots
Nuestro ecosistema
Ver todos los serviciosWORKENTA_OSSFESS_SOLUTIONSPRANALUZ_YOGAREMY_AI