Introducing Nuvyx UI v1.0.0

Scroll Animation Trigger

UI elements that change color, size, or shape based on scroll progress. Text that reveals dynamically when entering the viewport.

Scroll Animation Magic

Discover beautiful animations triggered by your scrolling journey

Fade In Effect

This content gently fades into view as you scroll down the page, creating a subtle and elegant appearance that draws attention without being distracting.

Scale Effect

Watch as this content smoothly scales from small to full size as you scroll, creating a dynamic entrance that captures attention and adds visual depth.

Scale StartScale Complete

Slide Up Effect

This content slides gracefully into view from below, creating a smooth transition that guides the eye naturally as you explore the page content.

SmoothElegantNatural

Custom Animation

This demonstrates a custom animation path combining multiple movements and rotations. Create your own unique entrance effects with complete creative freedom.

Movement
Diagonal Slide
Rotation
-10° to 0°

Color Change

Watch the text transform through vibrant colors as you scroll through this section, creating a playful and engaging visual experience tied to your scroll position.

Rotation Effect

This content spins into place as you scroll, adding a dynamic and playful element to the page that catches the eye.

Installation Guide

1

Install Dependencies

Framer Motion

npm install framer-motion

Utility Functions

npm install clsx tailwind-merge
2

Setup Configuration

Create file: /lib/utils.ts
/lib/utils.ts
Configuration
1import { clsx, type ClassValue } from "clsx";
2import { twMerge } from "tailwind-merge";
3
4  export function cn(...inputs: ClassValue[]) {
5      return twMerge(clsx(inputs));
6  }
3

Copy Component Code

Scroll Animation Trigger.tsx
TypeScript
1"use client";
2import { useRef, useEffect, useState, type ReactNode } from "react";
3import {
4  motion,
5  useScroll,
6  useTransform,
7  type MotionValue,
8} from "framer-motion";
9import { cn } from "@/lib/utils";
10
11export interface ScrollAnimationTriggerProps {
12  children: ReactNode;
13  className?: string;
14  effect?: "fade" | "scale" | "slide" | "color" | "rotate" | "custom";
15  threshold?: number;
16  delay?: number;
17  duration?: number;
18  direction?: "up" | "down" | "left" | "right";
19  once?: boolean;
20
21  // eslint-disable-next-line @typescript-eslint/no-explicit-any
22  customProps?: Record<string, any>;
23  as?: React.ElementType;
24  fromColor?: string;
25  toColor?: string;
26  fromRotation?: number;
27  toRotation?: number;
28  fromScale?: number;
29  toScale?: number;
30}
31
32export function ScrollAnimationTrigger({
33  children,
34  className,
35  effect = "fade",
36  threshold = 0.1,
37  delay = 0,
38  duration = 0.5,
39  direction = "up",
40  once = false,
41  customProps = {},
42  as = "div",
43  fromColor = "var(--color-muted)",
44  toColor = "var(--color-primary)",
45  fromRotation = direction === "left" ? -10 : 10,
46  toRotation = 0,
47  fromScale = 0.8,
48  toScale = 1,
49}: ScrollAnimationTriggerProps) {
50  const ref = useRef<HTMLDivElement>(null);
51  const [isInView, setIsInView] = useState(false);
52
53  const { scrollYProgress } = useScroll({
54    target: ref,
55    offset: ["start end", "end start"],
56  });
57
58  const textColor = useTransform(scrollYProgress, [0, 1], [fromColor, toColor]);
59  const rotation = useTransform(
60    scrollYProgress,
61    [0, 1],
62    [fromRotation, toRotation]
63  );
64
65  useEffect(() => {
66    if (!ref.current) return;
67    const observer = new IntersectionObserver(
68      (entries) => {
69        const [entry] = entries;
70        if (entry.isIntersecting) {
71          setIsInView(true);
72          if (once) observer.disconnect();
73        } else if (!once) {
74          setIsInView(false);
75        }
76      },
77      { threshold }
78    );
79
80    observer.observe(ref.current);
81    return () => observer.disconnect();
82  }, [threshold, once]);
83
84  const getAnimationProps = () => {
85    // eslint-disable-next-line @typescript-eslint/no-explicit-any
86    const baseProps: any = {
87      initial: {},
88      animate: {},
89      transition: { duration, delay, ease: "easeOut" },
90    };
91
92    switch (effect) {
93      case "fade":
94        baseProps.initial = { opacity: 0 };
95        baseProps.animate = isInView ? { opacity: 1 } : { opacity: 0 };
96        break;
97      case "scale":
98        baseProps.initial = { scale: fromScale, opacity: 0 };
99        baseProps.animate = isInView
100          ? { scale: toScale, opacity: 1 }
101          : { scale: fromScale, opacity: 0 };
102        break;
103      case "slide":
104        const offset = 50;
105        const directionMap = {
106          up: { y: offset },
107          down: { y: -offset },
108          left: { x: offset },
109          right: { x: -offset },
110        };
111        baseProps.initial = { ...directionMap[direction], opacity: 0 };
112        baseProps.animate = isInView
113          ? { x: 0, y: 0, opacity: 1 }
114          : { ...directionMap[direction], opacity: 0 };
115        break;
116      case "color":
117        baseProps.style = { color: textColor };
118        break;
119      case "rotate":
120        baseProps.style = { rotate: rotation, opacity: isInView ? 1 : 0 };
121        break;
122      case "custom":
123        return {
124          ...baseProps,
125          ...customProps,
126          animate: isInView
127            ? { ...customProps.animate }
128            : { ...customProps.initial },
129        };
130      default:
131        break;
132    }
133    return baseProps;
134  };
135
136  const MotionComponent =
137    as === "div"
138      ? motion.div
139      : as === "span"
140      ? motion.span
141      : as === "p"
142      ? motion.p
143      : as === "h1"
144      ? motion.h1
145      : as === "h2"
146      ? motion.h2
147      : as === "h3"
148      ? motion.h3
149      : as === "h4"
150      ? motion.h4
151      : as === "h5"
152      ? motion.h5
153      : as === "h6"
154      ? motion.h6
155      : as === "section"
156      ? motion.section
157      : as === "article"
158      ? motion.article
159      : as === "aside"
160      ? motion.aside
161      : as === "nav"
162      ? motion.nav
163      : as === "ul"
164      ? motion.ul
165      : as === "ol"
166      ? motion.ol
167      : as === "li"
168      ? motion.li
169      : as === "button"
170      ? motion.button
171      : motion.div;
172
173  return (
174    <MotionComponent
175      ref={ref}
176      className={cn("scroll-animation-trigger", className)}
177      {...getAnimationProps()}
178    >
179      {children}
180    </MotionComponent>
181  );
182}
183
184export function useScrollProgress(options = {}) {
185  const ref = useRef<HTMLDivElement>(null);
186  const { scrollYProgress } = useScroll({
187    target: ref,
188    offset: ["start end", "end start"],
189    ...options,
190  });
191  return { ref, scrollYProgress };
192}
193
194export function useScrollColor(
195  scrollYProgress: MotionValue<number>,
196  fromColor: string,
197  toColor: string
198) {
199  return useTransform(scrollYProgress, [0, 1], [fromColor, toColor]);
200}
201
202export function useScrollSize(
203  scrollYProgress: MotionValue<number>,
204  fromSize: number,
205  toSize: number
206) {
207  return useTransform(scrollYProgress, [0, 1], [fromSize, toSize]);
208}
209
210export function useScrollRotation(
211  scrollYProgress: MotionValue<number>,
212  fromRotation: number,
213  toRotation: number
214) {
215  return useTransform(scrollYProgress, [0, 1], [fromRotation, toRotation]);
216}
217
218export interface ScrollProgressAnimationProps {
219  children:
220    | ReactNode
221    | ((props: { scrollYProgress: MotionValue<number> }) => ReactNode);
222  className?: string;
223  offset?: ["start end", "end start"] | [string, string];
224}
225
226export function ScrollProgressAnimation({
227  children,
228  className,
229}: ScrollProgressAnimationProps) {
230  const ref = useRef<HTMLDivElement>(null);
231  const { scrollYProgress } = useScroll({
232    target: ref,
233    offset: ["start end", "end start"],
234  });
235
236  return (
237    <div ref={ref} className={cn("scroll-progress-animation", className)}>
238      {typeof children === "function"
239        ? children({ scrollYProgress })
240        : children}
241    </div>
242  );
243}
244
4

Final Steps

Update Import Paths

Make sure to update the import paths in the component code to match your project structure. For example, change @/components/ui/button to match your UI components location.

Props

NameTypeDefaultDescription
childrenReactNode-The content to be animated
effectstringfadeThe animation effect - (fade, scale, slide, color, rotate, custom)
thresholdnumber0.1The threshold for triggering the animation (0-1)
delaynumber0Delay before the animation starts (in seconds)
durationnumber0.5Duration of the animation (in seconds)
directionstringupDirection for slide animation - (up, down, left, right)
oncebooleanfalseWhether to trigger the animation only once
customPropsobject{}Custom animation properties for the 'custom' effect
asReact.ElementTypedivThe element type to render - (div, span, etc)
classNamestring-Additional CSS classes to apply
fromColorstringvar(--color-muted)Starting color for color transition
toColorstringvar(--color-primary)Ending color for color transition
fromRotationnumberdirection === "left" ? -10 : 10Initial rotation angle for rotate effect
toRotationnumber0Final rotation angle for rotate effect
fromScalenumber0.8Initial scale value for scale effect
toScalenumber1Final scale value for scale effect

Examples

Animation

Fade In Animation

This content smoothly fades into view as it enters the viewport, creating a clean and elegant transition that enhances the user experience.

Motion

Slide Up Animation

This content gracefully slides up into position as you scroll, creating a natural flow that guides the user through your content.

Smooth
Responsive
Premium

Custom Animation

This content uses complex custom animations with diagonal movement, rotation, and scaling for maximum visual impact.

Scale

Scale Animation

Watch as this content smoothly scales from small to full size as you scroll, creating an eye-catching effect that emphasizes important information.

Featured
Color

Color Change Animation

This content transforms through a beautiful color transition as it enters your view, creating a visually engaging experience.

Advanced

Slide From Left

This panel slides in from the left side of the screen.

Slide From Right

This panel slides in from the right side of the screen.

Scale Up

This panel scales up from a smaller size.

Fade In

This panel fades in after all others appear.