undefined
A curated collection of interactive React components and effects. Each component includes live demos, complete code examples, and implementation notes for easy integration into your projects.
Interactive text animation that responds to cursor movement. Each character dynamically changes weight, width, and opacity based on proximity to the cursor.
Font weight: 100-900
Character width: 75-125
Range: 0.3-1.0
/* HTML Structure */
<div id="textPressureContainer" class="relative w-full h-80 flex items-center justify-center">
<h1 id="textPressureTitle" class="text-6xl font-bold text-white uppercase tracking-wider cursor-default select-none">
PRESSURE
</h1>
</div>
/* CSS Styles */
<style>
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@100..900&display=swap');
.text-pressure-title {
font-family: 'Inter', sans-serif;
font-variation-settings: 'wght' 400, 'wdth' 100;
transition: all 0.1s ease-out;
text-shadow: 0 0 30px rgba(255, 255, 255, 0.3);
}
.text-pressure-title span {
display: inline-block;
transition: all 0.1s ease-out;
font-variation-settings: inherit;
}
</style>
/* JavaScript Implementation */
<script>
(function() {
const container = document.getElementById('textPressureContainer');
const title = document.getElementById('textPressureTitle');
if (!container || !title) return;
const text = title.textContent;
const chars = text.split('');
// Clear and rebuild with spans
title.innerHTML = '';
const spans = [];
chars.forEach((char, i) => {
const span = document.createElement('span');
span.textContent = char;
span.style.display = 'inline-block';
spans.push(span);
title.appendChild(span);
});
const mouse = { x: 0, y: 0 };
const cursor = { x: 0, y: 0 };
// Initialize cursor position
const containerRect = container.getBoundingClientRect();
mouse.x = cursor.x = containerRect.left + containerRect.width / 2;
mouse.y = cursor.y = containerRect.top + containerRect.height / 2;
// Distance calculation
const dist = (a, b) => {
const dx = b.x - a.x;
const dy = b.y - a.y;
return Math.sqrt(dx * dx + dy * dy);
};
// Mouse/touch event handlers
const handleMouseMove = (e) => {
cursor.x = e.clientX;
cursor.y = e.clientY;
};
const handleTouchMove = (e) => {
const touch = e.touches[0];
cursor.x = touch.clientX;
cursor.y = touch.clientY;
};
window.addEventListener('mousemove', handleMouseMove);
window.addEventListener('touchmove', handleTouchMove, { passive: false });
// Animation loop
let rafId;
const animate = () => {
// Smooth cursor following
mouse.x += (cursor.x - mouse.x) / 15;
mouse.y += (cursor.y - mouse.y) / 15;
const titleRect = title.getBoundingClientRect();
const maxDist = titleRect.width / 2;
spans.forEach((span) => {
const rect = span.getBoundingClientRect();
const charCenter = {
x: rect.x + rect.width / 2,
y: rect.y + rect.height / 2,
};
const d = dist(mouse, charCenter);
const getAttr = (distance, minVal, maxVal) => {
const val = maxVal - Math.abs((maxVal * distance) / maxDist);
return Math.max(minVal, val + minVal);
};
const wdth = Math.floor(getAttr(d, 75, 125));
const wght = Math.floor(getAttr(d, 100, 900));
const alphaVal = getAttr(d, 0.3, 1).toFixed(2);
span.style.opacity = alphaVal;
span.style.fontVariationSettings = `'wght' ${wght}, 'wdth' ${wdth}`;
});
rafId = requestAnimationFrame(animate);
};
animate();
// Cleanup function
const cleanup = () => {
if (rafId) {
cancelAnimationFrame(rafId);
}
window.removeEventListener('mousemove', handleMouseMove);
window.removeEventListener('touchmove', handleTouchMove);
};
window.textPressureCleanup = cleanup;
})();
</script>
Animated text that reveals with a blur-to-clear effect. Perfect for creating smooth, eye-catching text animations that trigger when scrolled into view.
Isn't this so cool?!
Character by Character
Beautiful blur animations made simple
Triggers when scrolled into view
Customizable delay between elements
Animate from top or bottom
Animate by words or characters
/* HTML Structure */
<div class="blur-text-container" data-delay="150" data-animate-by="words" data-direction="top">
<p class="text-4xl font-bold text-gray-800">
Isn't this so cool?!
</p>
</div>
/* CSS Styles */
<style>
.blur-text-container {
display: flex;
flex-wrap: wrap;
gap: 0;
}
.blur-text-container p {
display: flex;
flex-wrap: wrap;
gap: 0;
margin: 0;
}
.blur-text-segment {
display: inline-block;
transition: all 0.6s cubic-bezier(0.4, 0, 0.2, 1);
will-change: transform, filter, opacity;
}
.blur-text-segment.from-top {
filter: blur(10px);
opacity: 0;
transform: translateY(-20px);
}
.blur-text-segment.from-bottom {
filter: blur(10px);
opacity: 0;
transform: translateY(20px);
}
.blur-text-segment.animate-in {
filter: blur(0px);
opacity: 1;
transform: translateY(0);
}
</style>
/* JavaScript Implementation */
<script>
class BlurText {
constructor(container) {
this.container = container;
this.delay = parseInt(container.dataset.delay) || 150;
this.animateBy = container.dataset.animateBy || 'words';
this.direction = container.dataset.direction || 'top';
this.hasAnimated = false;
this.init();
}
init() {
const textElement = this.container.querySelector('p');
if (!textElement) return;
const text = textElement.textContent;
const segments = this.animateBy === 'words' ? text.split(' ') : text.split('');
// Clear and rebuild with spans
textElement.innerHTML = '';
this.segments = [];
segments.forEach((segment, index) => {
const span = document.createElement('span');
span.className = `blur-text-segment from-${this.direction}`;
span.textContent = segment;
if (this.animateBy === 'words' && index < segments.length - 1) {
span.textContent += ' ';
}
this.segments.push(span);
textElement.appendChild(span);
});
this.setupIntersectionObserver();
}
setupIntersectionObserver() {
const observer = new IntersectionObserver(
(entries) => {
entries.forEach(entry => {
if (entry.isIntersecting && !this.hasAnimated) {
this.animate();
this.hasAnimated = true;
}
});
},
{ threshold: 0.1, rootMargin: '0px' }
);
observer.observe(this.container);
this.observer = observer;
}
animate() {
this.segments.forEach((segment, index) => {
setTimeout(() => {
segment.classList.add('animate-in');
}, index * this.delay);
});
}
reset() {
this.hasAnimated = false;
this.segments.forEach(segment => {
segment.classList.remove('animate-in');
});
if (this.observer) {
this.observer.disconnect();
this.setupIntersectionObserver();
}
}
destroy() {
if (this.observer) {
this.observer.disconnect();
}
}
}
// Initialize all blur text components
document.addEventListener('DOMContentLoaded', () => {
const containers = document.querySelectorAll('.blur-text-container');
window.blurTextInstances = [];
containers.forEach(container => {
const instance = new BlurText(container);
window.blurTextInstances.push(instance);
});
});
// Reset function
function resetBlurAnimations() {
if (window.blurTextInstances) {
window.blurTextInstances.forEach(instance => {
instance.reset();
});
}
}
</script>
Animated circular text component with customizable rotation speed and hover effects. Perfect for logos, decorative elements, and interactive text displays.
Customizable rotation speed
Multiple hover interactions
Mobile-friendly design
Easy styling and theming
/* HTML Structure */
<div class="circular-text-container" data-text="REACT*BITS*COMPONENTS*" data-speed="20" data-hover="speedUp">
<div class="circular-text">
<!-- Letters will be generated dynamically -->
</div>
</div>
/* CSS Styles */
<style>
.circular-text-container {
display: flex;
align-items: center;
justify-content: center;
width: 200px;
height: 200px;
margin: 0 auto;
}
.circular-text {
position: relative;
width: 200px;
height: 200px;
border-radius: 50%;
transform-origin: 50% 50%;
animation: rotate 20s linear infinite;
cursor: pointer;
transition: animation-duration 0.5s ease;
}
.circular-text span {
position: absolute;
font-size: 18px;
font-weight: bold;
color: #059669;
transform-origin: 0 0;
transition: all 0.3s ease;
width: 20px;
height: 20px;
display: flex;
align-items: center;
justify-content: center;
}
.circular-text:hover span {
color: #047857;
transform: scale(1.1);
}
@keyframes rotate {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
@keyframes rotate-fast {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
@keyframes rotate-slow {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.speed-up {
animation: rotate-fast 5s linear infinite;
}
.slow-down {
animation: rotate-slow 40s linear infinite;
}
.paused {
animation-play-state: paused;
}
</style>
/* JavaScript Implementation */
<script>
class CircularText {
constructor(container) {
this.container = container;
this.textElement = container.querySelector('.circular-text');
this.text = container.dataset.text || 'CIRCULAR*TEXT*';
this.speed = parseInt(container.dataset.speed) || 20;
this.hoverEffect = container.dataset.hover || 'speedUp';
this.originalAnimationDuration = this.speed;
this.init();
}
init() {
this.createLetters();
this.setupAnimation();
this.bindEvents();
}
createLetters() {
const letters = Array.from(this.text);
this.textElement.innerHTML = '';
const radius = 80; // Distance from center to letters
const centerX = 100; // Center of the 200px container
const centerY = 100; // Center of the 200px container
letters.forEach((letter, index) => {
const span = document.createElement('span');
span.textContent = letter;
// Calculate angle for each letter
const angle = (360 / letters.length) * index;
const radian = (angle * Math.PI) / 180;
// Calculate position on circumference
const x = centerX + radius * Math.cos(radian);
const y = centerY + radius * Math.sin(radian);
// Position the letter and rotate it to face outward
span.style.left = `${x}px`;
span.style.top = `${y}px`;
span.style.transform = `translate(-50%, -50%) rotate(${angle + 90}deg)`;
this.textElement.appendChild(span);
});
}
setupAnimation() {
this.textElement.style.animationDuration = `${this.speed}s`;
}
bindEvents() {
this.textElement.addEventListener('mouseenter', () => {
this.handleHover();
});
this.textElement.addEventListener('mouseleave', () => {
this.resetAnimation();
});
}
handleHover() {
this.textElement.classList.remove('speed-up', 'slow-down', 'paused');
switch (this.hoverEffect) {
case 'speedUp':
this.textElement.classList.add('speed-up');
break;
case 'slowDown':
this.textElement.classList.add('slow-down');
break;
case 'pause':
this.textElement.classList.add('paused');
break;
case 'goBonkers':
this.textElement.style.animationDuration = '0.5s';
this.textElement.style.transform = 'scale(0.8)';
break;
}
}
resetAnimation() {
this.textElement.classList.remove('speed-up', 'slow-down', 'paused');
this.textElement.style.animationDuration = `${this.speed}s`;
this.textElement.style.transform = 'scale(1)';
}
updateText(newText) {
this.text = newText;
this.createLetters();
}
updateSpeed(newSpeed) {
this.speed = newSpeed;
this.setupAnimation();
}
destroy() {
this.textElement.removeEventListener('mouseenter', this.handleHover);
this.textElement.removeEventListener('mouseleave', this.resetAnimation);
}
}
// Initialize all circular text components
document.addEventListener('DOMContentLoaded', () => {
const containers = document.querySelectorAll('.circular-text-container');
window.circularTextInstances = [];
containers.forEach(container => {
const instance = new CircularText(container);
window.circularTextInstances.push(instance);
});
});
</script>
Add a glossy, shimmering effect to your text with customizable speed and intensity. Perfect for highlighting important content and creating eye-catching headings.
Adjustable animation speed
Enable/disable animation
Easy color and style changes
Pure CSS animations
/* HTML Structure */
<div class="shiny-text" data-speed="3" data-disabled="false">
Just some shiny text!
</div>
/* CSS Styles */
<style>
.shiny-text {
color: #b5b5b5a4;
background: linear-gradient(
120deg,
rgba(255, 255, 255, 0) 40%,
rgba(255, 255, 255, 0.8) 50%,
rgba(255, 255, 255, 0) 60%
);
background-size: 200% 100%;
-webkit-background-clip: text;
background-clip: text;
display: inline-block;
animation: shine 5s linear infinite;
}
@keyframes shine {
0% {
background-position: 100%;
}
100% {
background-position: -100%;
}
}
.shiny-text.disabled {
animation: none;
}
</style>
/* JavaScript Implementation */
<script>
class ShinyText {
constructor(element) {
this.element = element;
this.text = element.textContent;
this.speed = parseInt(element.dataset.speed) || 5;
this.disabled = element.dataset.disabled === 'true';
this.init();
}
init() {
this.setupAnimation();
this.applyState();
}
setupAnimation() {
this.element.style.animationDuration = `${this.speed}s`;
}
applyState() {
if (this.disabled) {
this.element.classList.add('disabled');
} else {
this.element.classList.remove('disabled');
}
}
updateSpeed(newSpeed) {
this.speed = newSpeed;
this.element.style.animationDuration = `${this.speed}s`;
}
enable() {
this.disabled = false;
this.element.classList.remove('disabled');
}
disable() {
this.disabled = true;
this.element.classList.add('disabled');
}
toggle() {
if (this.disabled) {
this.enable();
} else {
this.disable();
}
}
updateText(newText) {
this.text = newText;
this.element.textContent = newText;
}
destroy() {
this.element.classList.remove('shiny-text', 'disabled');
this.element.style.animationDuration = '';
}
}
// Initialize all shiny text components
document.addEventListener('DOMContentLoaded', () => {
const elements = document.querySelectorAll('.shiny-text');
window.shinyTextInstances = [];
elements.forEach(element => {
const instance = new ShinyText(element);
window.shinyTextInstances.push(instance);
});
});
// Helper function to create shiny text programmatically
function createShinyText(text, speed = 5, disabled = false) {
const element = document.createElement('span');
element.className = 'shiny-text';
element.textContent = text;
element.dataset.speed = speed;
element.dataset.disabled = disabled;
const instance = new ShinyText(element);
window.shinyTextInstances.push(instance);
return { element, instance };
}
</script>
Animate text by splitting it into individual characters, words, or lines with customizable stagger effects. Perfect for creating engaging text reveals and scroll-triggered animations.
Characters, words, or lines
Customizable timing delays
Animate on scroll into view
CSS-based animations
/* HTML Structure */
<div class="split-text-container" data-split="chars" data-delay="50" data-duration="0.6" data-trigger="scroll">
<h3 class="text-4xl font-bold text-gray-800">
Hello, Amazing World!
</h3>
</div>
/* CSS Styles */
<style>
.split-text-container {
overflow: hidden;
}
.split-text-container .split-char,
.split-text-container .split-word,
.split-text-container .split-line {
display: inline-block;
opacity: 0;
transform: translateY(40px);
transition: all 0.6s cubic-bezier(0.25, 0.46, 0.45, 0.94);
}
.split-text-container .split-char.animate,
.split-text-container .split-word.animate,
.split-text-container .split-line.animate {
opacity: 1;
transform: translateY(0);
}
.split-text-container .split-line {
display: block;
width: 100%;
}
.split-text-container .split-word {
margin-right: 0.25em;
}
.split-text-container .split-char {
margin-right: 0;
}
</style>
/* JavaScript Implementation */
<script>
class SplitText {
constructor(container) {
this.container = container;
this.textElement = container.querySelector('h3') || container.querySelector('p') || container.querySelector('span');
this.text = this.textElement.textContent;
this.splitType = container.dataset.split || 'chars';
this.delay = parseInt(container.dataset.delay) || 50;
this.duration = parseFloat(container.dataset.duration) || 0.6;
this.trigger = container.dataset.trigger || 'scroll';
this.hasAnimated = false;
this.init();
}
init() {
this.splitText();
this.setupTrigger();
}
splitText() {
const text = this.text;
this.textElement.innerHTML = '';
switch (this.splitType) {
case 'chars':
this.splitIntoChars(text);
break;
case 'words':
this.splitIntoWords(text);
break;
case 'lines':
this.splitIntoLines(text);
break;
default:
this.splitIntoChars(text);
}
}
splitIntoChars(text) {
const chars = Array.from(text);
chars.forEach((char, index) => {
const span = document.createElement('span');
span.textContent = char === ' ' ? '\u00A0' : char;
span.className = 'split-char';
span.style.transitionDelay = `${index * this.delay}ms`;
span.style.transitionDuration = `${this.duration}s`;
this.textElement.appendChild(span);
});
}
splitIntoWords(text) {
const words = text.split(' ');
words.forEach((word, index) => {
const span = document.createElement('span');
span.textContent = word;
span.className = 'split-word';
span.style.transitionDelay = `${index * this.delay}ms`;
span.style.transitionDuration = `${this.duration}s`;
this.textElement.appendChild(span);
});
}
splitIntoLines(text) {
const lines = text.split(/\r?\n/);
lines.forEach((line, index) => {
const span = document.createElement('span');
span.textContent = line;
span.className = 'split-line';
span.style.transitionDelay = `${index * this.delay}ms`;
span.style.transitionDuration = `${this.duration}s`;
this.textElement.appendChild(span);
});
}
setupTrigger() {
if (this.trigger === 'instant') {
this.animate();
} else {
this.setupIntersectionObserver();
}
}
setupIntersectionObserver() {
const observer = new IntersectionObserver(
(entries) => {
entries.forEach(entry => {
if (entry.isIntersecting && !this.hasAnimated) {
this.animate();
this.hasAnimated = true;
}
});
},
{ threshold: 0.1, rootMargin: '0px' }
);
observer.observe(this.container);
this.observer = observer;
}
animate() {
const elements = this.textElement.querySelectorAll('.split-char, .split-word, .split-line');
elements.forEach(element => {
element.classList.add('animate');
});
}
reset() {
this.hasAnimated = false;
const elements = this.textElement.querySelectorAll('.split-char, .split-word, .split-line');
elements.forEach(element => {
element.classList.remove('animate');
});
if (this.observer) {
this.observer.disconnect();
this.setupIntersectionObserver();
}
}
destroy() {
if (this.observer) {
this.observer.disconnect();
}
this.textElement.innerHTML = this.text;
}
}
// Initialize all split text components
document.addEventListener('DOMContentLoaded', () => {
const containers = document.querySelectorAll('.split-text-container');
window.splitTextInstances = [];
containers.forEach(container => {
const instance = new SplitText(container);
window.splitTextInstances.push(instance);
});
});
// Reset function for testing
function resetSplitAnimations() {
if (window.splitTextInstances) {
window.splitTextInstances.forEach(instance => {
instance.reset();
});
}
}
</script>
Create mesmerizing curved text animations that flow along customizable paths. Perfect for hero sections, artistic displays, and interactive text experiences.
Drag to control animation
Customizable curve intensity
Adjustable animation speed
Left or right movement
/* HTML Structure */
<div class="curved-loop-container" data-text="Welcome to React Bits ✦ " data-speed="2" data-curve="300" data-direction="left" data-interactive="true">
<svg class="curved-loop-svg" viewBox="0 0 1440 200">
<defs>
<path id="curve-1" d="M-100,100 Q720,400 1540,100" fill="none" stroke="transparent"/>
</defs>
<text font-weight="bold" fill="#10b981" class="curved-text">
<textPath href="#curve-1"></textPath>
</text>
</svg>
</div>
/* CSS Styles */
<style>
.curved-loop-container {
min-height: 200px;
display: flex;
align-items: center;
justify-content: center;
width: 100%;
cursor: grab;
}
.curved-loop-container.dragging {
cursor: grabbing;
}
.curved-loop-container.non-interactive {
cursor: default;
}
.curved-loop-svg {
user-select: none;
width: 100%;
height: 200px;
overflow: visible;
display: block;
font-size: 2rem;
font-weight: 700;
letter-spacing: 2px;
text-transform: uppercase;
line-height: 1;
}
.curved-text {
font-family: inherit;
}
</style>
/* JavaScript Implementation */
<script>
class CurvedLoop {
constructor(container) {
this.container = container;
this.text = container.dataset.text || 'Curved Text ✦ ';
this.speed = parseFloat(container.dataset.speed) || 2;
this.curve = parseInt(container.dataset.curve) || 300;
this.direction = container.dataset.direction || 'left';
this.interactive = container.dataset.interactive !== 'false';
this.textPath = container.querySelector('textPath');
this.path = container.querySelector('path');
this.svg = container.querySelector('svg');
this.isDragging = false;
this.lastMouseX = 0;
this.velocity = 0;
this.currentOffset = 0;
this.animationId = null;
this.spacing = 0;
this.pathLength = 0;
this.init();
}
init() {
this.setupPath();
this.setupText();
this.setupInteraction();
this.startAnimation();
}
setupPath() {
const pathD = `M-100,100 Q720,${100 + this.curve} 1540,100`;
this.path.setAttribute('d', pathD);
this.pathLength = this.path.getTotalLength();
}
setupText() {
// Create multiple text spans to create seamless loop
const textLength = this.text.length;
const svgWidth = 1440;
const charWidth = 20; // Approximate character width
this.spacing = charWidth;
const repeats = Math.ceil(this.pathLength / (textLength * charWidth)) + 3;
let textContent = '';
for (let i = 0; i < repeats; i++) {
textContent += this.text;
}
this.textPath.textContent = textContent;
// Set cursor style
if (this.interactive) {
this.container.classList.add('interactive');
} else {
this.container.classList.add('non-interactive');
}
}
setupInteraction() {
if (!this.interactive) return;
this.container.addEventListener('mousedown', this.onMouseDown.bind(this));
this.container.addEventListener('mousemove', this.onMouseMove.bind(this));
this.container.addEventListener('mouseup', this.onMouseUp.bind(this));
this.container.addEventListener('mouseleave', this.onMouseUp.bind(this));
// Touch events
this.container.addEventListener('touchstart', this.onTouchStart.bind(this));
this.container.addEventListener('touchmove', this.onTouchMove.bind(this));
this.container.addEventListener('touchend', this.onTouchEnd.bind(this));
}
onMouseDown(e) {
this.isDragging = true;
this.lastMouseX = e.clientX;
this.velocity = 0;
this.container.classList.add('dragging');
}
onMouseMove(e) {
if (!this.isDragging) return;
const deltaX = e.clientX - this.lastMouseX;
this.lastMouseX = e.clientX;
this.velocity = deltaX;
this.currentOffset += deltaX;
this.updateTextPosition();
}
onMouseUp() {
this.isDragging = false;
this.container.classList.remove('dragging');
// Set direction based on drag velocity
if (Math.abs(this.velocity) > 2) {
this.direction = this.velocity > 0 ? 'right' : 'left';
}
}
onTouchStart(e) {
this.onMouseDown({ clientX: e.touches[0].clientX });
}
onTouchMove(e) {
e.preventDefault();
this.onMouseMove({ clientX: e.touches[0].clientX });
}
onTouchEnd() {
this.onMouseUp();
}
updateTextPosition() {
const offset = this.currentOffset % (this.text.length * this.spacing);
this.textPath.setAttribute('startOffset', `${offset}px`);
}
startAnimation() {
const animate = () => {
if (!this.isDragging) {
const delta = this.direction === 'right' ? this.speed : -this.speed;
this.currentOffset += delta;
this.updateTextPosition();
}
this.animationId = requestAnimationFrame(animate);
};
animate();
}
destroy() {
if (this.animationId) {
cancelAnimationFrame(this.animationId);
}
// Remove event listeners
this.container.removeEventListener('mousedown', this.onMouseDown);
this.container.removeEventListener('mousemove', this.onMouseMove);
this.container.removeEventListener('mouseup', this.onMouseUp);
this.container.removeEventListener('mouseleave', this.onMouseUp);
this.container.removeEventListener('touchstart', this.onTouchStart);
this.container.removeEventListener('touchmove', this.onTouchMove);
this.container.removeEventListener('touchend', this.onTouchEnd);
}
}
// Initialize all curved loop components
document.addEventListener('DOMContentLoaded', () => {
const containers = document.querySelectorAll('.curved-loop-container');
window.curvedLoopInstances = [];
containers.forEach(container => {
const instance = new CurvedLoop(container);
window.curvedLoopInstances.push(instance);
});
});
</script>
Create stunning animated gradient text effects with customizable colors, speed, and optional borders. Perfect for headlines, call-to-actions, and brand statements.
Any color combination
Adjustable animation timing
Animated gradient borders
Pure CSS animations
/* React Component Implementation */
import React from 'react';
interface GradientTextProps {
children: React.ReactNode;
className?: string;
colors?: string[];
animationSpeed?: number;
showBorder?: boolean;
}
const GradientText: React.FC<GradientTextProps> = ({
children,
className = '',
colors = ['#40ffaa', '#4079ff', '#40ffaa', '#4079ff', '#40ffaa'],
animationSpeed = 8,
showBorder = false,
}) => {
const gradientColors = colors.join(', ');
const gradientStyle = `linear-gradient(90deg, ${gradientColors})`;
const containerClasses = [
'gradient-text-container',
showBorder ? 'has-border' : '',
className
].filter(Boolean).join(' ');
return (
<div
className={containerClasses}
style={{
...(showBorder ? {
background: gradientStyle,
backgroundSize: '300% 100%',
animationDuration: `${animationSpeed}s`
} : {})
}}
>
<div
className="text-content"
style={{
background: gradientStyle,
backgroundSize: '300% 100%',
animationDuration: `${animationSpeed}s`
}}
>
{children}
</div>
</div>
);
};
export default GradientText;
/* Usage Examples */
// Basic usage
<GradientText>
Add a splash of color!
</GradientText>
// Custom colors and speed
<GradientText
colors={['#ff0080', '#ff8c00', '#40ff00', '#00ff80', '#0080ff', '#8000ff']}
animationSpeed={4}
>
Rainbow Magic
</GradientText>
// With border and custom styling
<GradientText
colors={['#ff6b6b', '#ffa726', '#ffeb3b', '#ffa726', '#ff6b6b']}
animationSpeed={2}
showBorder={true}
className="text-4xl font-bold px-6 py-3"
>
Sunset Vibes
</GradientText>
// Fast animation
<GradientText
colors={['#e91e63', '#9c27b0', '#673ab7', '#9c27b0', '#e91e63']}
animationSpeed={1}
>
Lightning Fast!
</GradientText>
// Cyberpunk style with border
<GradientText
colors={['#00f5ff', '#ff00ff', '#00ff41', '#ff00ff', '#00f5ff']}
animationSpeed={2.5}
showBorder={true}
className="text-4xl font-bold px-8 py-4"
>
CYBER PUNK
</GradientText>
/* Required CSS Styles */
<style>
.gradient-text-container {
position: relative;
margin: 0 auto;
display: flex;
max-width: fit-content;
flex-direction: row;
align-items: center;
justify-content: center;
border-radius: 1.25rem;
font-weight: 500;
transition: all 0.3s ease;
overflow: hidden;
cursor: pointer;
}
.gradient-text-container.has-border {
background: linear-gradient(90deg, var(--gradient-colors, #40ffaa, #4079ff, #40ffaa, #4079ff, #40ffaa));
background-size: 300% 100%;
animation: gradient-border linear infinite;
padding: 3px;
}
.gradient-text-container.has-border::before {
content: "";
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
border-radius: calc(1.25rem - 3px);
width: calc(100% - 6px);
height: calc(100% - 6px);
background: linear-gradient(135deg, #0f0f0f 0%, #1a1a1a 100%);
z-index: 1;
}
.text-content {
display: inline-block;
position: relative;
z-index: 2;
background: linear-gradient(90deg, var(--gradient-colors, #40ffaa, #4079ff, #40ffaa, #4079ff, #40ffaa));
background-size: 300% 100%;
background-clip: text;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
color: transparent;
animation: gradient-text linear infinite;
font-weight: bold;
}
/* Specific gradient definitions for each demo */
.gradient-text-container[data-colors*="#40ffaa"] .text-content {
background: linear-gradient(90deg, #40ffaa, #4079ff, #40ffaa, #4079ff, #40ffaa);
background-size: 300% 100%;
background-clip: text;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
color: transparent;
}
.gradient-text-container[data-colors*="#ff0080"] .text-content {
background: linear-gradient(90deg, #ff0080, #ff8c00, #40ff00, #00ff80, #0080ff, #8000ff, #ff0080);
background-size: 300% 100%;
background-clip: text;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
color: transparent;
}
.gradient-text-container[data-colors*="#ff6b6b"] .text-content {
background: linear-gradient(90deg, #ff6b6b, #ffa726, #ffeb3b, #ffa726, #ff6b6b);
background-size: 300% 100%;
background-clip: text;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
color: transparent;
}
.gradient-text-container[data-colors*="#e91e63"] .text-content {
background: linear-gradient(90deg, #e91e63, #9c27b0, #673ab7, #9c27b0, #e91e63);
background-size: 300% 100%;
background-clip: text;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
color: transparent;
}
.gradient-text-container[data-colors*="#00f5ff"] .text-content {
background: linear-gradient(90deg, #00f5ff, #ff00ff, #00ff41, #ff00ff, #00f5ff);
background-size: 300% 100%;
background-clip: text;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
color: transparent;
}
/* Apply same gradients to borders */
.gradient-text-container.has-border[data-colors*="#40ffaa"] {
background: linear-gradient(90deg, #40ffaa, #4079ff, #40ffaa, #4079ff, #40ffaa);
background-size: 300% 100%;
}
.gradient-text-container.has-border[data-colors*="#ff6b6b"] {
background: linear-gradient(90deg, #ff6b6b, #ffa726, #ffeb3b, #ffa726, #ff6b6b);
background-size: 300% 100%;
}
.gradient-text-container.has-border[data-colors*="#00f5ff"] {
background: linear-gradient(90deg, #00f5ff, #ff00ff, #00ff41, #ff00ff, #00f5ff);
background-size: 300% 100%;
}
/* Animation speeds */
.gradient-text-container[data-speed="1"] .text-content,
.gradient-text-container[data-speed="1"].has-border {
animation-duration: 1s;
}
.gradient-text-container[data-speed="2"] .text-content,
.gradient-text-container[data-speed="2"].has-border {
animation-duration: 2s;
}
.gradient-text-container[data-speed="2.5"] .text-content,
.gradient-text-container[data-speed="2.5"].has-border {
animation-duration: 2.5s;
}
.gradient-text-container[data-speed="3"] .text-content,
.gradient-text-container[data-speed="3"].has-border {
animation-duration: 3s;
}
.gradient-text-container[data-speed="4"] .text-content,
.gradient-text-container[data-speed="4"].has-border {
animation-duration: 4s;
}
@keyframes gradient-text {
0% {
background-position: 0% 50%;
}
50% {
background-position: 100% 50%;
}
100% {
background-position: 0% 50%;
}
}
@keyframes gradient-border {
0% {
background-position: 0% 50%;
}
50% {
background-position: 100% 50%;
}
100% {
background-position: 0% 50%;
}
}
/* Hover effects */
.gradient-text-container:hover {
/* No hover effects */
}
/* Fallback for browsers that don't support background-clip: text */
@supports not (-webkit-background-clip: text) {
.text-content {
background: none !important;
-webkit-text-fill-color: initial !important;
color: #40ffaa !important;
}
}
/* Ensure text is visible for accessibility */
.text-content::selection {
background: rgba(64, 255, 170, 0.3);
-webkit-text-fill-color: #40ffaa;
}
</style>
/* Props Documentation */
interface GradientTextProps {
/**
* The content to be displayed inside the gradient text.
*/
children: React.ReactNode;
/**
* Adds custom classes to the root element for additional styling.
* @default ''
*/
className?: string;
/**
* Defines the gradient colors for the text or border.
* @default ['#40ffaa', '#4079ff', '#40ffaa', '#4079ff', '#40ffaa']
*/
colors?: string[];
/**
* The duration of the gradient animation in seconds.
* @default 8
*/
animationSpeed?: number;
/**
* Determines whether a border with the gradient effect is displayed.
* @default false
*/
showBorder?: boolean;
}
/* Installation & Setup */
// 1. Copy the GradientText component
// 2. Add the required CSS styles
// 3. Import and use in your React application
// Example with Tailwind CSS classes
<GradientText
className="text-6xl font-extrabold"
colors={['#667eea', '#764ba2', '#f093fb', '#f5576c']}
animationSpeed={3}
showBorder={true}
>
Beautiful Gradient Text
</GradientText>
Add delightful spark animations that trigger on user clicks. Customize colors, size, count, and animation duration for engaging interactive experiences.
Activates on user clicks
Any hex color support
Control spark dimensions
Duration & easing options
/* React Component */
import { useRef, useEffect, useCallback } from "react";
const ClickSpark = ({
sparkColor = "#fff",
sparkSize = 10,
sparkRadius = 15,
sparkCount = 8,
duration = 400,
easing = "ease-out",
extraScale = 1.0,
children
}) => {
const canvasRef = useRef(null);
const sparksRef = useRef([]);
const startTimeRef = useRef(null);
useEffect(() => {
const canvas = canvasRef.current;
if (!canvas) return;
const parent = canvas.parentElement;
if (!parent) return;
let resizeTimeout;
const resizeCanvas = () => {
const { width, height } = parent.getBoundingClientRect();
if (canvas.width !== width || canvas.height !== height) {
canvas.width = width;
canvas.height = height;
}
};
const handleResize = () => {
clearTimeout(resizeTimeout);
resizeTimeout = setTimeout(resizeCanvas, 100);
};
const ro = new ResizeObserver(handleResize);
ro.observe(parent);
resizeCanvas();
return () => {
ro.disconnect();
clearTimeout(resizeTimeout);
};
}, []);
const easeFunc = useCallback(
(t) => {
switch (easing) {
case "linear":
return t;
case "ease-in":
return t * t;
case "ease-in-out":
return t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t;
default:
return t * (2 - t);
}
},
[easing]
);
useEffect(() => {
const canvas = canvasRef.current;
if (!canvas) return;
const ctx = canvas.getContext("2d");
let animationId;
const draw = (timestamp) => {
if (!startTimeRef.current) {
startTimeRef.current = timestamp;
}
ctx.clearRect(0, 0, canvas.width, canvas.height);
sparksRef.current = sparksRef.current.filter((spark) => {
const elapsed = timestamp - spark.startTime;
if (elapsed >= duration) {
return false;
}
const progress = elapsed / duration;
const eased = easeFunc(progress);
const distance = eased * sparkRadius * extraScale;
const lineLength = sparkSize * (1 - eased);
const x1 = spark.x + distance * Math.cos(spark.angle);
const y1 = spark.y + distance * Math.sin(spark.angle);
const x2 = spark.x + (distance + lineLength) * Math.cos(spark.angle);
const y2 = spark.y + (distance + lineLength) * Math.sin(spark.angle);
ctx.strokeStyle = sparkColor;
ctx.lineWidth = 2;
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.stroke();
return true;
});
animationId = requestAnimationFrame(draw);
};
animationId = requestAnimationFrame(draw);
return () => {
cancelAnimationFrame(animationId);
};
}, [
sparkColor,
sparkSize,
sparkRadius,
sparkCount,
duration,
easeFunc,
extraScale,
]);
const handleClick = (e) => {
const canvas = canvasRef.current;
if (!canvas) return;
const rect = canvas.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
const now = performance.now();
const newSparks = Array.from({ length: sparkCount }, (_, i) => ({
x,
y,
angle: (2 * Math.PI * i) / sparkCount,
startTime: now,
}));
sparksRef.current.push(...newSparks);
};
return (
<div
style={{
position: 'relative',
width: '100%',
height: '100%'
}}
onClick={handleClick}
>
<canvas
ref={canvasRef}
style={{
width: "100%",
height: "100%",
display: "block",
userSelect: "none",
position: "absolute",
top: 0,
left: 0,
pointerEvents: "none"
}}
/>
{children}
</div>
);
};
export default ClickSpark;
/* Usage Examples */
// Basic usage
<ClickSpark>
<button>Click me!</button>
</ClickSpark>
// Custom properties
<ClickSpark
sparkColor="#ff6b6b"
sparkSize={12}
sparkRadius={20}
sparkCount={10}
duration={500}
easing="ease-in-out"
>
<div className="interactive-content">
Custom spark effect!
</div>
</ClickSpark>
// Fast, small sparks
<ClickSpark
sparkColor="#00ff88"
sparkSize={6}
sparkRadius={10}
sparkCount={16}
duration={250}
>
<span>Quick sparks!</span>
</ClickSpark>
// Large, slow sparks
<ClickSpark
sparkColor="#fbbf24"
sparkSize={15}
sparkRadius={30}
sparkCount={6}
duration={800}
easing="ease-out"
>
<div>Big dramatic sparks!</div>
</ClickSpark>
Create stunning curved image galleries with WebGL rendering. Features smooth scrolling, interactive navigation, and customizable bend effects for immersive visual experiences.
This component requires the OGL WebGL library to be installed in
your project. Install it with:
npm install ogl
Hardware-accelerated 3D rendering
Drag, scroll, and touch controls
Customizable bend effects
Works on all devices
/* Installation */
npm install ogl
/* React Component */
import { Camera, Mesh, Plane, Program, Renderer, Texture, Transform } from "ogl";
import { useEffect, useRef } from "react";
// Complete implementation with all classes and functionality
// ... (Full code would be very long, showing condensed version)
const CircularGallery = ({
items,
bend = 3,
textColor = "#ffffff",
borderRadius = 0.05,
font = "bold 30px Figtree",
scrollSpeed = 2,
scrollEase = 0.05,
}) => {
const containerRef = useRef(null);
useEffect(() => {
const app = new App(containerRef.current, {
items,
bend,
textColor,
borderRadius,
font,
scrollSpeed,
scrollEase
});
return () => {
app.destroy();
};
}, [items, bend, textColor, borderRadius, font, scrollSpeed, scrollEase]);
return (
<div
className="w-full h-full overflow-hidden cursor-grab active:cursor-grabbing"
ref={containerRef}
/>
);
};
export default CircularGallery;
/* Usage Examples */
// Basic usage with default images
<div style={{ height: '600px', position: 'relative' }}>
<CircularGallery />
</div>
// Custom gallery with your images
<div style={{ height: '600px', position: 'relative' }}>
<CircularGallery
bend={3}
textColor="#ffffff"
borderRadius={0.05}
scrollEase={0.02}
items={[
{ image: "/path/to/image1.jpg", text: "Image 1" },
{ image: "/path/to/image2.jpg", text: "Image 2" },
{ image: "/path/to/image3.jpg", text: "Image 3" }
]}
/>
</div>
// Flat layout (no curve)
<div style={{ height: '600px', position: 'relative' }}>
<CircularGallery
bend={0}
textColor="#000000"
borderRadius={0.1}
scrollSpeed={1}
items={customImages}
/>
</div>
// Extreme curve effect
<div style={{ height: '600px', position: 'relative' }}>
<CircularGallery
bend={8}
textColor="#ffffff"
borderRadius={0.02}
scrollSpeed={3}
scrollEase={0.1}
/>
</div>
/* Key Features */
// - WebGL-powered 3D rendering with OGL library
// - Interactive drag, scroll, and touch controls
// - Customizable curve effects (bend parameter)
// - Responsive design that adapts to container size
// - Smooth scrolling with configurable easing
// - Support for custom images and text labels
// - Automatic image aspect ratio handling
// - Rounded corners with border radius control
// - Performance optimized with proper cleanup
Create dynamic rotating text animations with Framer Motion. Features customizable transitions, stagger effects, and multiple animation modes for engaging text displays and hero sections.
For React Projects: This component requires
Framer Motion to be installed:
npm install framer-motion
For Regular Websites: You can't directly use React libraries on static HTML sites. However, I can create CSS-only versions of these animations that work without any external dependencies!
💡 Want this on your Landingsite.ai website? Ask me to create a pure CSS version that works without any libraries!
Framer Motion powered transitions
Character, word, and line staggering
Timing, transitions, and styling
Screen reader friendly
/* Installation */
npm install framer-motion
/* React Component */
"use client";
import {
forwardRef,
useCallback,
useEffect,
useImperativeHandle,
useMemo,
useState,
} from "react";
import { motion, AnimatePresence } from "framer-motion";
function cn(...classes) {
return classes.filter(Boolean).join(" ");
}
const RotatingText = forwardRef((props, ref) => {
const {
texts,
transition = { type: "spring", damping: 25, stiffness: 300 },
initial = { y: "100%", opacity: 0 },
animate = { y: 0, opacity: 1 },
exit = { y: "-120%", opacity: 0 },
animatePresenceMode = "wait",
animatePresenceInitial = false,
rotationInterval = 2000,
staggerDuration = 0,
staggerFrom = "first",
loop = true,
auto = true,
splitBy = "characters",
onNext,
mainClassName,
splitLevelClassName,
elementLevelClassName,
...rest
} = props;
const [currentTextIndex, setCurrentTextIndex] = useState(0);
const splitIntoCharacters = (text) => {
if (typeof Intl !== "undefined" && Intl.Segmenter) {
const segmenter = new Intl.Segmenter("en", { granularity: "grapheme" });
return Array.from(segmenter.segment(text), (segment) => segment.segment);
}
return Array.from(text);
};
const elements = useMemo(() => {
const currentText = texts[currentTextIndex];
if (splitBy === "characters") {
const words = currentText.split(" ");
return words.map((word, i) => ({
characters: splitIntoCharacters(word),
needsSpace: i !== words.length - 1,
}));
}
if (splitBy === "words") {
return currentText.split(" ").map((word, i, arr) => ({
characters: [word],
needsSpace: i !== arr.length - 1,
}));
}
if (splitBy === "lines") {
return currentText.split("\n").map((line, i, arr) => ({
characters: [line],
needsSpace: i !== arr.length - 1,
}));
}
return currentText.split(splitBy).map((part, i, arr) => ({
characters: [part],
needsSpace: i !== arr.length - 1,
}));
}, [texts, currentTextIndex, splitBy]);
const getStaggerDelay = useCallback(
(index, totalChars) => {
const total = totalChars;
if (staggerFrom === "first") return index * staggerDuration;
if (staggerFrom === "last") return (total - 1 - index) * staggerDuration;
if (staggerFrom === "center") {
const center = Math.floor(total / 2);
return Math.abs(center - index) * staggerDuration;
}
if (staggerFrom === "random") {
const randomIndex = Math.floor(Math.random() * total);
return Math.abs(randomIndex - index) * staggerDuration;
}
return Math.abs(staggerFrom - index) * staggerDuration;
},
[staggerFrom, staggerDuration]
);
const handleIndexChange = useCallback(
(newIndex) => {
setCurrentTextIndex(newIndex);
if (onNext) onNext(newIndex);
},
[onNext]
);
const next = useCallback(() => {
const nextIndex =
currentTextIndex === texts.length - 1
? loop
? 0
: currentTextIndex
: currentTextIndex + 1;
if (nextIndex !== currentTextIndex) {
handleIndexChange(nextIndex);
}
}, [currentTextIndex, texts.length, loop, handleIndexChange]);
const previous = useCallback(() => {
const prevIndex =
currentTextIndex === 0
? loop
? texts.length - 1
: currentTextIndex
: currentTextIndex - 1;
if (prevIndex !== currentTextIndex) {
handleIndexChange(prevIndex);
}
}, [currentTextIndex, texts.length, loop, handleIndexChange]);
const jumpTo = useCallback(
(index) => {
const validIndex = Math.max(0, Math.min(index, texts.length - 1));
if (validIndex !== currentTextIndex) {
handleIndexChange(validIndex);
}
},
[texts.length, currentTextIndex, handleIndexChange]
);
const reset = useCallback(() => {
if (currentTextIndex !== 0) {
handleIndexChange(0);
}
}, [currentTextIndex, handleIndexChange]);
useImperativeHandle(
ref,
() => ({
next,
previous,
jumpTo,
reset,
}),
[next, previous, jumpTo, reset]
);
useEffect(() => {
if (!auto) return;
const intervalId = setInterval(next, rotationInterval);
return () => clearInterval(intervalId);
}, [next, rotationInterval, auto]);
return (
<motion.span
className={cn(
"flex flex-wrap whitespace-pre-wrap relative",
mainClassName
)}
{...rest}
layout
transition={transition}
>
<span className="sr-only">{texts[currentTextIndex]}</span>
<AnimatePresence mode={animatePresenceMode} initial={animatePresenceInitial}>
<motion.span
key={currentTextIndex}
className={cn(
splitBy === "lines"
? "flex flex-col w-full"
: "flex flex-wrap whitespace-pre-wrap relative"
)}
layout
aria-hidden="true"
>
{elements.map((wordObj, wordIndex, array) => {
const previousCharsCount = array
.slice(0, wordIndex)
.reduce((sum, word) => sum + word.characters.length, 0);
return (
<span key={wordIndex} className={cn("inline-flex", splitLevelClassName)}>
{wordObj.characters.map((char, charIndex) => (
<motion.span
key={charIndex}
initial={initial}
animate={animate}
exit={exit}
transition={{
...transition,
delay: getStaggerDelay(
previousCharsCount + charIndex,
array.reduce((sum, word) => sum + word.characters.length, 0)
),
}}
className={cn("inline-block", elementLevelClassName)}
>
{char}
</motion.span>
))}
{wordObj.needsSpace && <span className="whitespace-pre"> </span>}
</span>
);
})}
</motion.span>
</AnimatePresence>
</motion.span>
);
});
RotatingText.displayName = "RotatingText";
export default RotatingText;
/* Usage Examples */
// Basic character stagger
<RotatingText
texts={['React', 'Bits', 'Is', 'Cool!']}
mainClassName="text-4xl font-bold text-violet-600"
staggerFrom="first"
staggerDuration={0.025}
rotationInterval={2000}
/>
// Word-by-word rotation
<RotatingText
texts={['Building', 'Creating', 'Designing', 'Crafting']}
splitBy="words"
mainClassName="text-3xl font-semibold text-purple-700"
initial={{ opacity: 0, scale: 0.8 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.8 }}
transition={{ type: "spring", damping: 20, stiffness: 200 }}
/>
// Styled badge with background
<RotatingText
texts={['Modern', 'Clean', 'Beautiful', 'Responsive']}
mainClassName="px-4 py-2 bg-gradient-to-r from-violet-500 to-purple-500 text-white rounded-lg font-bold"
staggerFrom="center"
staggerDuration={0.03}
splitLevelClassName="overflow-hidden"
rotationInterval={1500}
/>
// Large display with gradient text
<RotatingText
texts={['Amazing', 'Stunning', 'Incredible', 'Fantastic']}
mainClassName="text-6xl font-extrabold text-transparent bg-clip-text bg-gradient-to-r from-violet-400 to-fuchsia-400"
staggerFrom="last"
staggerDuration={0.05}
initial={{ y: "100%", rotateX: 90 }}
animate={{ y: 0, rotateX: 0 }}
exit={{ y: "-100%", rotateX: -90 }}
transition={{ type: "spring", damping: 30, stiffness: 400 }}
/>
// Custom controlled rotation
const rotatingTextRef = useRef(null);
const handleNext = () => {
rotatingTextRef.current?.next();
};
const handlePrevious = () => {
rotatingTextRef.current?.previous();
};
<div className="flex items-center gap-4">
<button onClick={handlePrevious}>Previous</button>
<RotatingText
ref={rotatingTextRef}
texts={['React', 'Vue', 'Angular', 'Svelte']}
auto={false}
mainClassName="text-2xl font-bold text-gray-800"
/>
<button onClick={handleNext}>Next</button>
</div>
// Line-by-line rotation
<RotatingText
texts={[
'First line\nSecond line\nThird line',
'Line A\nLine B\nLine C',
'Alpha\nBeta\nGamma'
]}
splitBy="lines"
mainClassName="text-xl font-medium text-center"
staggerFrom="first"
staggerDuration={0.1}
rotationInterval={3000}
/>