primotion
v1.0.3
Published
A lightweight React library for adding spring animations to any element
Downloads
5
Maintainers
Readme
Primotion
A lightweight, performant React library for adding smooth spring animations to any element. Built with TypeScript and optimized for modern React applications.
✨ Features
- 🎯 Simple API - Easy to use hooks and components
- ⚡ High Performance - Uses
requestAnimationFramefor smooth 60fps animations - 🎨 Flexible - Animate any CSS property with spring physics
- 📦 Lightweight - No external dependencies, just React
- 🔧 TypeScript - Full TypeScript support with type safety
- 🎪 Preset Configurations - Pre-configured spring settings for common use cases
- 🚀 Zero Configuration - Works out of the box with any React project
📦 Installation
npm install primotion
# or
yarn add primotion
# or
pnpm add primotion🚀 Quick Start
Using the useSpring Hook
import React, { useState } from 'react';
import { useSpring } from 'primotion';
function AnimatedCounter() {
const [count, setCount] = useState(0);
const { value, animate } = useSpring({ from: 0, to: count });
const increment = () => {
setCount(prev => prev + 1);
animate(count + 1);
};
return (
<div>
<h1>{Math.round(value)}</h1>
<button onClick={increment}>Increment</button>
</div>
);
}Using Pre-built Components
import React from 'react';
import { FadeIn, SlideIn } from 'primotion';
function App() {
return (
<div>
<FadeIn delay={200}>
<h1>This will fade in!</h1>
</FadeIn>
<SlideIn direction="up" delay={400}>
<p>This will slide up from below!</p>
</SlideIn>
</div>
);
}Using the Spring Component
import React from 'react';
import { Spring } from 'primotion';
function AnimatedBox() {
return (
<Spring from={0} to={100} immediate>
<div style={{ padding: '20px', background: 'blue', color: 'white' }}>
This box will slide down from the top!
</div>
</Spring>
);
}📚 API Reference
useSpring(options)
A React hook that provides spring animation functionality.
Options
| Property | Type | Default | Description |
|----------|------|---------|-------------|
| from | number | 0 | Starting value |
| to | number | 0 | Target value |
| config | SpringConfig | {} | Spring configuration |
| immediate | boolean | false | Start animation immediately |
| delay | number | 0 | Delay before starting animation (ms) |
| onUpdate | (value: number) => void | undefined | Callback on each animation frame |
| onComplete | () => void | undefined | Callback when animation completes |
Returns
| Property | Type | Description |
|----------|------|-------------|
| value | number | Current animated value |
| setValue | (value: number) => void | Set value immediately |
| animate | (to: number, config?: SpringConfig) => void | Animate to a new value |
| stop | () => void | Stop current animation |
| isAnimating | boolean | Whether animation is currently running |
Spring Component
A component that wraps elements with spring animation capabilities.
Props
| Property | Type | Default | Description |
|----------|------|---------|-------------|
| from | number | 0 | Starting value |
| to | number | 0 | Target value |
| config | SpringConfig | {} | Spring configuration |
| immediate | boolean | false | Start animation immediately |
| delay | number | 0 | Delay before starting animation |
| onUpdate | (value: number) => void | undefined | Update callback |
| onComplete | () => void | undefined | Complete callback |
| style | React.CSSProperties | {} | Additional styles |
| className | string | '' | CSS class name |
| as | keyof JSX.IntrinsicElements | 'div' | HTML element to render |
FadeIn Component
A component for fade-in animations.
Props
| Property | Type | Default | Description |
|----------|------|---------|-------------|
| children | ReactNode | - | Content to animate |
| duration | number | 500 | Animation duration (ms) |
| delay | number | 0 | Delay before starting |
| config | SpringConfig | {} | Spring configuration |
| style | React.CSSProperties | {} | Additional styles |
| className | string | '' | CSS class name |
| as | keyof JSX.IntrinsicElements | 'div' | HTML element to render |
SlideIn Component
A component for slide-in animations.
Props
| Property | Type | Default | Description |
|----------|------|---------|-------------|
| children | ReactNode | - | Content to animate |
| direction | 'up' \| 'down' \| 'left' \| 'right' | 'up' | Slide direction |
| distance | number | 50 | Distance to slide (px) |
| delay | number | 0 | Delay before starting |
| config | SpringConfig | {} | Spring configuration |
| style | React.CSSProperties | {} | Additional styles |
| className | string | '' | CSS class name |
| as | keyof JSX.IntrinsicElements | 'div' | HTML element to render |
⚙️ Spring Configuration
SpringConfig Interface
interface SpringConfig {
tension?: number; // Spring stiffness (default: 170)
friction?: number; // Damping factor (default: 26)
mass?: number; // Mass of the spring (default: 1)
damping?: number; // Additional damping (default: 0)
stiffness?: number; // Alternative to tension (default: 100)
}Preset Configurations
import { springPresets } from 'primotion';
// Available presets
springPresets.gentle // { tension: 120, friction: 14 }
springPresets.wobbly // { tension: 180, friction: 12 }
springPresets.stiff // { tension: 210, friction: 20 }
springPresets.slow // { tension: 40, friction: 10 }
springPresets.default // { tension: 170, friction: 26 }🎯 Examples
Animated Counter
import React, { useState } from 'react';
import { useSpring } from 'primotion';
function Counter() {
const [count, setCount] = useState(0);
const { value, animate } = useSpring({ from: 0, to: count });
const increment = () => {
setCount(prev => prev + 1);
animate(count + 1);
};
return (
<div>
<h1>{Math.round(value)}</h1>
<button onClick={increment}>Increment</button>
</div>
);
}Staggered Animations
import React from 'react';
import { FadeIn } from 'primotion';
function StaggeredList() {
const items = ['Item 1', 'Item 2', 'Item 3', 'Item 4'];
return (
<div>
{items.map((item, index) => (
<FadeIn key={item} delay={index * 100}>
<div style={{ padding: '10px', margin: '5px', background: '#f0f0f0' }}>
{item}
</div>
</FadeIn>
))}
</div>
);
}Custom Spring Animation
import React from 'react';
import { useSpring } from 'primotion';
function CustomAnimation() {
const { value, animate } = useSpring({
from: 0,
to: 1,
config: { tension: 200, friction: 15 },
immediate: true,
});
return (
<div>
<div
style={{
width: '100px',
height: '100px',
background: 'linear-gradient(45deg, #ff6b6b, #4ecdc4)',
borderRadius: '50%',
transform: `scale(${value})`,
transition: 'none',
}}
/>
<button onClick={() => animate(value === 1 ? 0.5 : 1)}>
Toggle Scale
</button>
</div>
);
}Interactive Hover Effects
import React, { useState } from 'react';
import { useSpring } from 'primotion';
function AnimatedList() {
const [hoveredIndex, setHoveredIndex] = useState<number | null>(null);
const items = ['Apple', 'Banana', 'Cherry', 'Date'];
return (
<div>
{items.map((item, index) => {
const { value, animate } = useSpring({
from: 0,
to: hoveredIndex === index ? 1 : 0,
config: { tension: 150, friction: 20 },
});
return (
<div
key={item}
onMouseEnter={() => setHoveredIndex(index)}
onMouseLeave={() => setHoveredIndex(null)}
style={{
padding: '15px',
margin: '5px',
background: '#f8f9fa',
borderRadius: '8px',
transform: `translateX(${value * 20}px)`,
transition: 'none',
}}
>
{item}
</div>
);
})}
</div>
);
}Loading Spinner
import React from 'react';
import { useSpring } from 'primotion';
function LoadingSpinner() {
const { value } = useSpring({
from: 0,
to: 360,
config: { tension: 100, friction: 20 },
immediate: true,
});
return (
<div
style={{
width: '40px',
height: '40px',
border: '4px solid #f3f3f3',
borderTop: '4px solid #007bff',
borderRadius: '50%',
transform: `rotate(${value}deg)`,
}}
/>
);
}Progress Bar
import React, { useState } from 'react';
import { useSpring } from 'primotion';
function ProgressBar() {
const [progress, setProgress] = useState(0);
const { value, animate } = useSpring({
from: 0,
to: progress,
config: { tension: 150, friction: 20 },
});
const updateProgress = (newProgress: number) => {
setProgress(newProgress);
animate(newProgress);
};
return (
<div>
<div
style={{
width: '300px',
height: '20px',
background: '#f0f0f0',
borderRadius: '10px',
overflow: 'hidden',
}}
>
<div
style={{
width: `${value}%`,
height: '100%',
background: '#007bff',
transition: 'none',
}}
/>
</div>
<button onClick={() => updateProgress(25)}>25%</button>
<button onClick={() => updateProgress(50)}>50%</button>
<button onClick={() => updateProgress(75)}>75%</button>
<button onClick={() => updateProgress(100)}>100%</button>
</div>
);
}Card Flip Animation
import React, { useState } from 'react';
import { useSpring } from 'primotion';
function AnimatedCard() {
const [isFlipped, setIsFlipped] = useState(false);
const { value, animate } = useSpring({
from: 0,
to: isFlipped ? 180 : 0,
config: { tension: 200, friction: 15 },
});
return (
<div
style={{
perspective: '1000px',
width: '200px',
height: '300px',
}}
>
<div
style={{
width: '100%',
height: '100%',
transform: `rotateY(${value}deg)`,
transformStyle: 'preserve-3d',
transition: 'none',
}}
>
{/* Front side */}
<div
style={{
position: 'absolute',
width: '100%',
height: '100%',
background: '#007bff',
color: 'white',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
borderRadius: '8px',
backfaceVisibility: 'hidden',
}}
>
Front
</div>
{/* Back side */}
<div
style={{
position: 'absolute',
width: '100%',
height: '100%',
background: '#28a745',
color: 'white',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
borderRadius: '8px',
transform: 'rotateY(180deg)',
backfaceVisibility: 'hidden',
}}
>
Back
</div>
</div>
<button
onClick={() => {
setIsFlipped(!isFlipped);
animate(isFlipped ? 0 : 180);
}}
style={{ marginTop: '10px' }}
>
Flip Card
</button>
</div>
);
}🚀 Performance Tips
- Use
immediate: falsefor animations that should start on user interaction - Batch animations using delays for staggered effects
- Clean up animations by calling
stop()in useEffect cleanup - Use
requestAnimationFrame(already handled by the library) - Avoid animating layout-heavy properties like
widthandheight - Use transform properties for better performance
🌐 Browser Support
- React 16.8+ (for hooks support)
- Modern browsers with
requestAnimationFramesupport - TypeScript 4.0+ (for type definitions)
📄 License
MIT License - see LICENSE file for details.
🤝 Contributing
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add some amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
📝 Changelog
1.0.1
- Initial release
- Core spring animation engine
useSpringhookSpring,FadeIn,SlideIncomponents- TypeScript support
- Preset configurations
