Logo

Kaif UI

logoKaif UI
Components

Scroll Velocity

Scroll Velocity

Dynamic scroll-based motion for engaging user experiences.

Image 1
Image 2
Image 3
Image 4
Image 5
Image 1 second row
Image 2 second row
Image 3 second row
Image 4 second row
Image 5 second row
Image 1
Image 2
Image 3
Image 4
Image 5
Image 1 second row
Image 2 second row
Image 3 second row
Image 4 second row
Image 5 second row

Scroll Velocity on Text

Scroll Velocity Text - Scroll Velocity TextScroll Velocity Text - Scroll Velocity TextScroll Velocity Text - Scroll Velocity TextScroll Velocity Text - Scroll Velocity TextScroll Velocity Text - Scroll Velocity Text

Installation

Install Dependencies

npm i framer-motion

Copy the source code

components/kaif-ui/scroll-velocity.tsx
// Visit https://kaif-ui.vercel.app/ for more components like this
 
import { ReactNode, useRef } from "react";
import {
  motion,
  useScroll,
  useSpring,
  useTransform,
  useMotionValue,
  useVelocity,
  useAnimationFrame,
  wrap,
} from "framer-motion";
 
interface Props {
  children: ReactNode[] | string;
  velocity: number;
  movable?: boolean;
  clamp?: boolean;
}
 
export default function ScrollVelocity({
  children,
  velocity = 5,
  movable = true,
  clamp = false,
}: Props) {
  const baseX = useMotionValue(0);
  const { scrollY } = useScroll();
  const scrollVelocity = useVelocity(scrollY);
  const smoothVelocity = useSpring(scrollVelocity, {
    damping: 50,
    stiffness: 100,
  });
  const velocityFactor = useTransform(smoothVelocity, [0, 10000], [0, 5], {
    clamp,
  });
 
  const x = useTransform(baseX, (v) => `${wrap(0, -50, v)}%`);
 
  const directionFactor = useRef<number>(1);
  const scrollThreshold = useRef<number>(5);
 
  useAnimationFrame((t, delta) => {
    if (movable) {
      move(delta);
    } else {
      if (Math.abs(scrollVelocity.get()) >= scrollThreshold.current) {
        move(delta);
      }
    }
  });
 
  function move(delta: number) {
    let moveBy = directionFactor.current * velocity * (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);
  }
 
  return (
    <div className="relative m-0 flex flex-nowrap overflow-hidden whitespace-nowrap leading-[0.8] tracking-[-2px]">
      <motion.div
        className="flex flex-row flex-nowrap whitespace-nowrap text-2xl font-semibold uppercase *:mr-6 *:block md:text-3xl xl:text-5xl"
        style={{ x }}
      >
        {typeof children === "string" ? (
          <>
            {Array.from({ length: 5 }).map((_, idx) => (
              <span key={idx}>{children}</span>
            ))}
          </>
        ) : (
          children
        )}
      </motion.div>
    </div>
  );
}

Tech Stack

Props

PropTypeDefault
children
ReactNode[] | string
undefined
velocity
number
5
movable
boolean
true
clamp
boolean
false

On this page

Brought to you by Muhammad Kaif Nazeer