The ShinyText component creates an animated gradient text effect with a moving shine that sweeps across the text.
Overview
ShinyText provides:
- Animated gradient sweep effect
- Customizable colors, speed, and direction
- Yoyo (back-and-forth) animation mode
- Pause on hover functionality
- Delay support for staggered animations
Props
The text content to display with the shine effect
Disables the animation when true
Animation duration in seconds
Additional CSS classes to apply
Base text color (gradient start/end)
Highlight color for the shine effect
Gradient angle in degrees (0-360)
Enables back-and-forth animation
Pauses animation when hovering over text
direction
'left' | 'right'
default:"'left'"
Direction of the shine sweep
Delay before animation starts (in seconds)
Implementation
import { useState, useCallback, useEffect, useRef } from 'react';
import { motion, useMotionValue, useAnimationFrame, useTransform } from 'motion/react';
const ShinyText = ({
text,
disabled = false,
speed = 2,
className = '',
color = '#b5b5b5',
shineColor = '#ffffff',
spread = 120,
yoyo = false,
pauseOnHover = false,
direction = 'left',
delay = 0
}) => {
const [isPaused, setIsPaused] = useState(false);
const progress = useMotionValue(0);
const elapsedRef = useRef(0);
const lastTimeRef = useRef(null);
const directionRef = useRef(direction === 'left' ? 1 : -1);
const animationDuration = speed * 1000;
const delayDuration = delay * 1000;
useAnimationFrame(time => {
if (disabled || isPaused) {
lastTimeRef.current = null;
return;
}
if (lastTimeRef.current === null) {
lastTimeRef.current = time;
return;
}
const deltaTime = time - lastTimeRef.current;
lastTimeRef.current = time;
elapsedRef.current += deltaTime;
if (yoyo) {
const cycleDuration = animationDuration + delayDuration;
const fullCycle = cycleDuration * 2;
const cycleTime = elapsedRef.current % fullCycle;
if (cycleTime < animationDuration) {
const p = (cycleTime / animationDuration) * 100;
progress.set(directionRef.current === 1 ? p : 100 - p);
} else if (cycleTime < cycleDuration) {
progress.set(directionRef.current === 1 ? 100 : 0);
} else if (cycleTime < cycleDuration + animationDuration) {
const reverseTime = cycleTime - cycleDuration;
const p = 100 - (reverseTime / animationDuration) * 100;
progress.set(directionRef.current === 1 ? p : 100 - p);
} else {
progress.set(directionRef.current === 1 ? 0 : 100);
}
} else {
const cycleDuration = animationDuration + delayDuration;
const cycleTime = elapsedRef.current % cycleDuration;
if (cycleTime < animationDuration) {
const p = (cycleTime / animationDuration) * 100;
progress.set(directionRef.current === 1 ? p : 100 - p);
} else {
progress.set(directionRef.current === 1 ? 100 : 0);
}
}
});
const backgroundPosition = useTransform(
progress,
p => `${150 - p * 2}% center`
);
const gradientStyle = {
backgroundImage: `linear-gradient(${spread}deg, ${color} 0%, ${color} 35%, ${shineColor} 50%, ${color} 65%, ${color} 100%)`,
backgroundSize: '200% auto',
WebkitBackgroundClip: 'text',
backgroundClip: 'text',
WebkitTextFillColor: 'transparent'
};
return (
<motion.span
className={`inline-block ${className}`}
style={{ ...gradientStyle, backgroundPosition }}
onMouseEnter={() => pauseOnHover && setIsPaused(true)}
onMouseLeave={() => pauseOnHover && setIsPaused(false)}
>
{text}
</motion.span>
);
};
export default ShinyText;
Usage Examples
Basic Usage
import ShinyText from './components/ShinyText/ShinyText';
function Hero() {
return (
<h1>
<ShinyText text="MUSIC STORE" />
</h1>
);
}
Custom Colors and Speed
<ShinyText
text="ENCUENTRA TU SONIDO IDEAL"
speed={4}
color="#c9a84c"
shineColor="#ffe566"
spread={120}
direction="left"
/>
Yoyo Animation
<ShinyText
text="Featured Product"
yoyo={true}
speed={3}
pauseOnHover={true}
/>
With Delay (Staggered)
<div>
<ShinyText text="LINE 1" delay={0} />
<ShinyText text="LINE 2" delay={0.5} />
<ShinyText text="LINE 3" delay={1.0} />
</div>
Animation Behavior
Standard Mode
The shine sweeps from one side to the other, then resets:
- Shine enters from off-screen
- Moves across the text
- Exits off-screen
- Delay period (if specified)
- Repeats
Yoyo Mode
The shine sweeps back and forth:
- Forward sweep
- Delay at end
- Reverse sweep
- Delay at start
- Repeats
Gradient Mechanics
The component uses CSS gradient background with text clipping:
background-image: linear-gradient(
{spread}deg,
{color} 0%,
{color} 35%,
{shineColor} 50%,
{color} 65%,
{color} 100%
);
background-size: 200% auto;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
The background position animates from 150% to -50% to create the sweep effect.
Real-World Example
From Hero component:
<ShinyText
text={"ENCUENTRA TU SONIDO\nIDEAL CON NOSOTROS"}
speed={4}
delay={0}
color="#c9a84c"
shineColor="#ffe566"
spread={120}
direction="left"
yoyo={false}
pauseOnHover={false}
disabled={false}
className="whitespace-pre text-sm md:text-base font-bold"
style={{
fontFamily: "'Bebas Neue', sans-serif",
letterSpacing: "0.10em"
}}
/>
Use Framer Motion’s useAnimationFrame instead of requestAnimationFrame for better integration with React’s rendering cycle.
The component requires the motion library from Framer Motion for smooth animation performance.