React Animation & Transitions Guide

Master animations in React from simple CSS transitions to complex physics-based animations. Learn performance optimization techniques and best practices for smooth 60fps animations.

Why Animations Matter in Modern Web Apps

Animations are not just about making your app look pretty. They serve crucial purposes in user experience:

  • Provide Visual Feedback: Animations communicate state changes and confirm user actions
  • Guide User Attention: Smooth transitions help users understand where to focus
  • Create Spatial Awareness: Motion helps users understand how UI elements relate to each other
  • Enhance Perceived Performance: Well-crafted animations can make your app feel faster and more responsive
  • Add Personality: Unique animations can differentiate your brand and create memorable experiences

In React, we have multiple approaches to implement animations, each with its own strengths. This guide will help you choose the right tool for your specific use case and implement it effectively.

Advertisement Space - top-animations

Google AdSense: horizontal

Try Interactive Demos

CSS Transitions & Animations

Using pure CSS for smooth transitions and keyframe animations in React

CSS transitions and animations are the foundation of web animations. They're performant, well-supported, and perfect for simple to moderately complex animations. In React, you can control these animations by toggling classes or inline styles based on component state.

When to use CSS Transitions & Animations:

  • Hover effects and micro-interactions
  • Simple state transitions (show/hide, expand/collapse)
  • Loading spinners and progress indicators
  • Keyframe-based animations that don't need JavaScript control

Advantages:

  • Zero JavaScript overhead during animation
  • Browser-optimized performance
  • Works even if JavaScript fails
  • Smaller bundle size

Implementation Example

1// CSS Transitions in React
2import { useState } from 'react';
3import './styles.css';
4
5const CSSTransitionExample = () => {
6 const [isExpanded, setIsExpanded] = useState(false);
7 const [isVisible, setIsVisible] = useState(true);
8 const [activeTab, setActiveTab] = useState(0);
9
10 return (
11 <div className="animation-demo">
12 {/* Simple toggle transition */}
13 <button
14 className="toggle-button"
15 onClick={() => setIsExpanded(!isExpanded)}
16 >
17 Toggle Expand
18 </button>
19
20 <div className={`expandable-content ${isExpanded ? 'expanded' : ''}`}>
21 <p>This content expands and collapses smoothly</p>
22 </div>
23
24 {/* Fade in/out */}
25 <button onClick={() => setIsVisible(!isVisible)}>
26 Toggle Visibility
27 </button>
28
29 <div className={`fade-box ${isVisible ? 'visible' : ''}`}>
30 Fade In/Out Box
31 </div>
32
33 {/* Sliding tabs */}
34 <div className="tabs-container">
35 <div className="tab-buttons">
36 {['Tab 1', 'Tab 2', 'Tab 3'].map((tab, index) => (
37 <button
38 key={index}
39 className={`tab-button ${activeTab === index ? 'active' : ''}`}
40 onClick={() => setActiveTab(index)}
41 >
42 {tab}
43 </button>
44 ))}
45 </div>
46
47 <div className="tab-indicator"
48 style={{ transform: `translateX(${activeTab * 100}%)` }}
49 />
50
51 <div className="tab-content">
52 <div
53 className="tab-panels"
54 style={{ transform: `translateX(-${activeTab * 100}%)` }}
55 >
56 <div className="tab-panel">Content 1</div>
57 <div className="tab-panel">Content 2</div>
58 <div className="tab-panel">Content 3</div>
59 </div>
60 </div>
61 </div>
62 </div>
63 );
64};

CSS Styles

1/* CSS for smooth transitions */
2.toggle-button {
3 background: #3b82f6;
4 color: white;
5 padding: 0.5rem 1rem;
6 border: none;
7 border-radius: 0.25rem;
8 cursor: pointer;
9 transition: all 0.3s ease;
10}
11
12.toggle-button:hover {
13 background: #2563eb;
14 transform: translateY(-2px);
15 box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
16}
17
18.expandable-content {
19 max-height: 0;
20 overflow: hidden;
21 opacity: 0;
22 transition: all 0.5s cubic-bezier(0.4, 0, 0.2, 1);
23}
24
25.expandable-content.expanded {
26 max-height: 200px;
27 opacity: 1;
28 padding: 1rem;
29}
30
31.fade-box {
32 width: 200px;
33 height: 100px;
34 background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
35 border-radius: 0.5rem;
36 opacity: 0;
37 transform: scale(0.8);
38 transition: all 0.4s ease;
39 pointer-events: none;
40}
41
42.fade-box.visible {
43 opacity: 1;
44 transform: scale(1);
45 pointer-events: auto;
46}
47
48/* Sliding tabs */
49.tabs-container {
50 position: relative;
51 width: 100%;
52 background: #f3f4f6;
53 border-radius: 0.5rem;
54 overflow: hidden;
55}
56
57.tab-buttons {
58 display: flex;
59 position: relative;
60 z-index: 1;
61}
62
63.tab-button {
64 flex: 1;
65 padding: 1rem;
66 background: transparent;
67 border: none;
68 cursor: pointer;
69 transition: color 0.3s ease;
70}
71
72.tab-button.active {
73 color: #3b82f6;
74}
75
76.tab-indicator {
77 position: absolute;
78 top: 0;
79 left: 0;
80 width: 33.333%;
81 height: 3px;
82 background: #3b82f6;
83 transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
84}
85
86.tab-content {
87 position: relative;
88 overflow: hidden;
89 height: 200px;
90}
91
92.tab-panels {
93 display: flex;
94 transition: transform 0.3s ease;
95}
96
97.tab-panel {
98 width: 100%;
99 flex-shrink: 0;
100 padding: 2rem;
101}
102
103/* Keyframe animations */
104@keyframes slideIn {
105 from {
106 transform: translateX(-100%);
107 opacity: 0;
108 }
109 to {
110 transform: translateX(0);
111 opacity: 1;
112 }
113}
114
115@keyframes bounce {
116 0%, 100% {
117 transform: translateY(0);
118 }
119 50% {
120 transform: translateY(-20px);
121 }
122}
123
124@keyframes pulse {
125 0% {
126 transform: scale(1);
127 opacity: 1;
128 }
129 50% {
130 transform: scale(1.05);
131 opacity: 0.8;
132 }
133 100% {
134 transform: scale(1);
135 opacity: 1;
136 }
137}
138
139.animate-slide-in {
140 animation: slideIn 0.5s ease forwards;
141}
142
143.animate-bounce {
144 animation: bounce 1s ease infinite;
145}
146
147.animate-pulse {
148 animation: pulse 2s ease infinite;
149}

Best Practices

  • Use transform and opacity for best performance
  • Prefer cubic-bezier for natural motion
  • Avoid animating layout properties (width, height)
  • Use will-change sparingly for optimization
  • Test animations on lower-end devices

React Transition Group

Managing component transitions with React Transition Group library

React Transition Group is the official animation library maintained by the React team. It provides components to manage the lifecycle of animations, especially for elements entering and leaving the DOM.

When to use React Transition Group:

  • Animating components as they mount/unmount
  • Managing lists where items are added/removed
  • Creating page transitions in React Router
  • When you need fine control over animation stages

Key Components:

  • CSSTransition: Applies CSS classes during animation stages
  • TransitionGroup: Manages a set of transition components
  • SwitchTransition: Transitions between two elements
  • Transition: Low-level component for custom implementations

Implementation Example

1// React Transition Group examples
2import { useState } from 'react';
3import { CSSTransition, TransitionGroup, SwitchTransition } from 'react-transition-group';
4import './transitions.css';
5
6const TransitionGroupExample = () => {
7 const [items, setItems] = useState([
8 { id: 1, text: 'Item 1' },
9 { id: 2, text: 'Item 2' },
10 { id: 3, text: 'Item 3' }
11 ]);
12 const [showMessage, setShowMessage] = useState(false);
13 const [mode, setMode] = useState('out-in');
14
15 const addItem = () => {
16 const newItem = {
17 id: Date.now(),
18 text: `Item ${items.length + 1}`
19 };
20 setItems([...items, newItem]);
21 };
22
23 const removeItem = (id) => {
24 setItems(items.filter(item => item.id !== id));
25 };
26
27 return (
28 <div>
29 {/* CSSTransition for single elements */}
30 <div className="section">
31 <h3>CSSTransition Example</h3>
32 <button onClick={() => setShowMessage(!showMessage)}>
33 Toggle Message
34 </button>
35
36 <CSSTransition
37 in={showMessage}
38 timeout={300}
39 classNames="message"
40 unmountOnExit
41 >
42 <div className="message-box">
43 <p>This message fades in and out!</p>
44 </div>
45 </CSSTransition>
46 </div>
47
48 {/* TransitionGroup for lists */}
49 <div className="section">
50 <h3>TransitionGroup Example</h3>
51 <button onClick={addItem}>Add Item</button>
52
53 <TransitionGroup className="todo-list">
54 {items.map(item => (
55 <CSSTransition
56 key={item.id}
57 timeout={500}
58 classNames="item"
59 >
60 <div className="list-item">
61 <span>{item.text}</span>
62 <button onClick={() => removeItem(item.id)}>×</button>
63 </div>
64 </CSSTransition>
65 ))}
66 </TransitionGroup>
67 </div>
68
69 {/* SwitchTransition for replacing content */}
70 <div className="section">
71 <h3>SwitchTransition Example</h3>
72 <div className="mode-selector">
73 <label>
74 <input
75 type="radio"
76 value="out-in"
77 checked={mode === 'out-in'}
78 onChange={(e) => setMode(e.target.value)}
79 />
80 out-in
81 </label>
82 <label>
83 <input
84 type="radio"
85 value="in-out"
86 checked={mode === 'in-out'}
87 onChange={(e) => setMode(e.target.value)}
88 />
89 in-out
90 </label>
91 </div>
92
93 <SwitchTransition mode={mode}>
94 <CSSTransition
95 key={showMessage ? 'message' : 'button'}
96 timeout={300}
97 classNames="switch"
98 >
99 {showMessage ? (
100 <div className="switch-content" onClick={() => setShowMessage(false)}>
101 <h4>Message View</h4>
102 <p>Click to go back</p>
103 </div>
104 ) : (
105 <div className="switch-content" onClick={() => setShowMessage(true)}>
106 <h4>Button View</h4>
107 <p>Click to see message</p>
108 </div>
109 )}
110 </CSSTransition>
111 </SwitchTransition>
112 </div>
113 </div>
114 );
115};
116
117// Advanced: Custom transition component
118const FadeTransition = ({ children, ...props }) => {
119 const nodeRef = useRef(null);
120
121 return (
122 <CSSTransition
123 nodeRef={nodeRef}
124 timeout={300}
125 classNames="fade"
126 {...props}
127 >
128 <div ref={nodeRef}>
129 {children}
130 </div>
131 </CSSTransition>
132 );
133};

CSS Styles

1/* React Transition Group styles */
2/* CSSTransition classes */
3.message-enter {
4 opacity: 0;
5 transform: scale(0.9);
6}
7
8.message-enter-active {
9 opacity: 1;
10 transform: scale(1);
11 transition: opacity 300ms, transform 300ms;
12}
13
14.message-exit {
15 opacity: 1;
16}
17
18.message-exit-active {
19 opacity: 0;
20 transform: scale(0.9);
21 transition: opacity 300ms, transform 300ms;
22}
23
24/* TransitionGroup list animations */
25.item-enter {
26 opacity: 0;
27 transform: translateX(-30px);
28}
29
30.item-enter-active {
31 opacity: 1;
32 transform: translateX(0);
33 transition: all 500ms ease;
34}
35
36.item-exit {
37 opacity: 1;
38}
39
40.item-exit-active {
41 opacity: 0;
42 transform: translateX(30px);
43 transition: all 500ms ease;
44}
45
46/* SwitchTransition animations */
47.switch-enter {
48 opacity: 0;
49 transform: translateY(20px);
50}
51
52.switch-enter-active {
53 opacity: 1;
54 transform: translateY(0);
55 transition: all 300ms ease;
56}
57
58.switch-exit {
59 opacity: 1;
60 transform: translateY(0);
61}
62
63.switch-exit-active {
64 opacity: 0;
65 transform: translateY(-20px);
66 transition: all 300ms ease;
67}
68
69/* Custom fade transition */
70.fade-enter {
71 opacity: 0;
72}
73
74.fade-enter-active {
75 opacity: 1;
76 transition: opacity 300ms ease-in;
77}
78
79.fade-exit {
80 opacity: 1;
81}
82
83.fade-exit-active {
84 opacity: 0;
85 transition: opacity 300ms ease-out;
86}

Best Practices

  • Use nodeRef to avoid findDOMNode warnings
  • Set unique keys for TransitionGroup items
  • Use unmountOnExit to remove DOM elements
  • Combine with CSS modules for scoped styles
  • Test exit animations thoroughly

Framer Motion

Creating advanced animations with Framer Motion's declarative API

Framer Motion is a production-ready animation library that brings powerful animations to React with a declarative API. It handles complex animations, gestures, and layout transitions with ease.

When to use Framer Motion:

  • Complex gesture-based interactions (drag, pinch, hover)
  • Layout animations (animating between different layouts)
  • Scroll-triggered animations
  • SVG path animations
  • When you need a comprehensive animation solution

Key Features:

  • Declarative API: Animations are defined as props
  • Gesture Support: Built-in drag, tap, hover, and pan gestures
  • Layout Animations: Smooth transitions between layout changes
  • Variants: Reusable animation states
  • AnimatePresence: Exit animations for unmounting components

Implementation Example

1// Framer Motion advanced animations
2import { motion, AnimatePresence, useAnimation, useInView } from 'framer-motion';
3import { useState, useRef, useEffect } from 'react';
4
5const FramerMotionExample = () => {
6 const [isOpen, setIsOpen] = useState(false);
7 const [selectedId, setSelectedId] = useState(null);
8 const controls = useAnimation();
9 const ref = useRef(null);
10 const isInView = useInView(ref);
11
12 useEffect(() => {
13 if (isInView) {
14 controls.start('visible');
15 }
16 }, [controls, isInView]);
17
18 // Animation variants
19 const containerVariants = {
20 hidden: { opacity: 0 },
21 visible: {
22 opacity: 1,
23 transition: {
24 delayChildren: 0.3,
25 staggerChildren: 0.2
26 }
27 }
28 };
29
30 const itemVariants = {
31 hidden: { y: 20, opacity: 0 },
32 visible: {
33 y: 0,
34 opacity: 1,
35 transition: {
36 type: 'spring',
37 damping: 12,
38 stiffness: 100
39 }
40 }
41 };
42
43 // Layout animation items
44 const items = [
45 { id: 1, title: 'Card 1', color: '#ff6b6b' },
46 { id: 2, title: 'Card 2', color: '#4ecdc4' },
47 { id: 3, title: 'Card 3', color: '#45b7d1' },
48 { id: 4, title: 'Card 4', color: '#f9ca24' }
49 ];
50
51 return (
52 <div>
53 {/* Basic animations */}
54 <motion.div
55 initial={{ opacity: 0, scale: 0.5 }}
56 animate={{ opacity: 1, scale: 1 }}
57 transition={{ duration: 0.5 }}
58 className="basic-box"
59 >
60 <h3>Basic Animation</h3>
61 </motion.div>
62
63 {/* Hover and tap animations */}
64 <motion.button
65 whileHover={{ scale: 1.1 }}
66 whileTap={{ scale: 0.9 }}
67 transition={{ type: 'spring', stiffness: 400, damping: 17 }}
68 className="interactive-button"
69 >
70 Interactive Button
71 </motion.button>
72
73 {/* Gesture animations */}
74 <motion.div
75 drag
76 dragConstraints={{ left: -100, right: 100, top: -100, bottom: 100 }}
77 dragElastic={0.2}
78 whileDrag={{ scale: 1.2 }}
79 className="draggable-box"
80 >
81 Drag me!
82 </motion.div>
83
84 {/* Stagger animations */}
85 <motion.ul
86 ref={ref}
87 variants={containerVariants}
88 initial="hidden"
89 animate={controls}
90 className="stagger-list"
91 >
92 {['Item 1', 'Item 2', 'Item 3', 'Item 4'].map((item, i) => (
93 <motion.li
94 key={i}
95 variants={itemVariants}
96 className="stagger-item"
97 >
98 {item}
99 </motion.li>
100 ))}
101 </motion.ul>
102
103 {/* Layout animations */}
104 <div className="layout-grid">
105 {items.map(item => (
106 <motion.div
107 key={item.id}
108 layoutId={item.id}
109 onClick={() => setSelectedId(item.id)}
110 style={{ backgroundColor: item.color }}
111 className="layout-card"
112 whileHover={{ scale: 1.05 }}
113 whileTap={{ scale: 0.95 }}
114 >
115 <motion.h4>{item.title}</motion.h4>
116 </motion.div>
117 ))}
118 </div>
119
120 {/* AnimatePresence for exit animations */}
121 <AnimatePresence>
122 {selectedId && (
123 <motion.div
124 layoutId={selectedId}
125 className="expanded-card"
126 initial={{ opacity: 0 }}
127 animate={{ opacity: 1 }}
128 exit={{ opacity: 0 }}
129 onClick={() => setSelectedId(null)}
130 >
131 <motion.h2>Expanded View</motion.h2>
132 <motion.p>Click to close</motion.p>
133 </motion.div>
134 )}
135 </AnimatePresence>
136
137 {/* Path animations */}
138 <motion.svg width="200" height="200" viewBox="0 0 200 200">
139 <motion.path
140 d="M20,50 Q80,10 140,50 T200,50"
141 stroke="#ff6b6b"
142 strokeWidth="3"
143 fill="none"
144 initial={{ pathLength: 0 }}
145 animate={{ pathLength: 1 }}
146 transition={{ duration: 2, repeat: Infinity, repeatType: 'reverse' }}
147 />
148 </motion.svg>
149
150 {/* Complex orchestrated animation */}
151 <motion.div
152 className="orchestrated-container"
153 initial="hidden"
154 animate="visible"
155 variants={{
156 hidden: { opacity: 0 },
157 visible: {
158 opacity: 1,
159 transition: {
160 when: 'beforeChildren',
161 staggerChildren: 0.1
162 }
163 }
164 }}
165 >
166 {[1, 2, 3, 4].map(i => (
167 <motion.div
168 key={i}
169 className="orchestrated-item"
170 variants={{
171 hidden: { x: -50, opacity: 0 },
172 visible: {
173 x: 0,
174 opacity: 1,
175 transition: {
176 type: 'spring',
177 damping: 12,
178 stiffness: 100
179 }
180 }
181 }}
182 />
183 ))}
184 </motion.div>
185 </div>
186 );
187};
188
189// Custom hook for scroll-triggered animations
190const useScrollAnimation = () => {
191 const controls = useAnimation();
192 const ref = useRef(null);
193 const isInView = useInView(ref, { once: true, amount: 0.3 });
194
195 useEffect(() => {
196 if (isInView) {
197 controls.start('visible');
198 }
199 }, [controls, isInView]);
200
201 return { ref, controls };
202};

Best Practices

  • Use variants for reusable animations
  • Leverage layout animations for smooth transitions
  • AnimatePresence enables exit animations
  • Combine with useInView for scroll animations
  • Use spring physics for natural motion

React Spring

Physics-based animations with React Spring for fluid interactions

React Spring brings spring-physics to React animations, creating fluid and natural motion. Unlike duration-based animations, spring animations feel more organic and respond better to user interaction.

When to use React Spring:

  • When you want physics-based, natural animations
  • Complex animation sequences and chains
  • Animating multiple properties in sync
  • Parallax effects and scroll animations
  • When performance is critical (uses React's render cycle efficiently)

Key Concepts:

  • Springs: Core animation primitive with physics
  • Trails: Staggered animations for lists
  • Chains: Sequential animation orchestration
  • Parallax: Built-in parallax scrolling support
  • Gesture Integration: Works seamlessly with drag and scroll

Implementation Example

1// React Spring animations
2import { useSpring, animated, useTrail, useChain, useSpringRef, useSprings } from '@react-spring/web';
3import { useState, useRef } from 'react';
4
5const ReactSpringExample = () => {
6 const [open, setOpen] = useState(false);
7 const [items] = useState([
8 { id: 1, text: 'Spring Item 1' },
9 { id: 2, text: 'Spring Item 2' },
10 { id: 3, text: 'Spring Item 3' },
11 { id: 4, text: 'Spring Item 4' }
12 ]);
13
14 // Basic spring animation
15 const basicSpring = useSpring({
16 from: { opacity: 0, transform: 'scale(0.5)' },
17 to: { opacity: 1, transform: 'scale(1)' },
18 config: { tension: 280, friction: 60 }
19 });
20
21 // Toggle animation
22 const toggleSpring = useSpring({
23 opacity: open ? 1 : 0,
24 transform: open ? 'translateY(0px)' : 'translateY(-40px)',
25 config: { mass: 1, tension: 210, friction: 20 }
26 });
27
28 // Trail animation for lists
29 const trail = useTrail(items.length, {
30 from: { opacity: 0, x: -20 },
31 to: { opacity: 1, x: 0 },
32 config: { mass: 1, tension: 280, friction: 60 }
33 });
34
35 // Chained animations
36 const springRef = useSpringRef();
37 const spring1 = useSpring({
38 ref: springRef,
39 from: { width: '0%' },
40 to: { width: open ? '100%' : '0%' }
41 });
42
43 const trailRef = useSpringRef();
44 const trail2 = useTrail(3, {
45 ref: trailRef,
46 from: { scale: 0 },
47 to: { scale: open ? 1 : 0 }
48 });
49
50 // Chain the animations
51 useChain(open ? [springRef, trailRef] : [trailRef, springRef], [
52 0,
53 open ? 0.1 : 0.6
54 ]);
55
56 // Multiple springs for complex animations
57 const [springs, api] = useSprings(4, index => ({
58 from: { scale: 0, rotation: 0 },
59 to: { scale: 1, rotation: 360 },
60 delay: index * 100
61 }));
62
63 // Gesture-based animation
64 const [{ x, y }, api2] = useSpring(() => ({
65 x: 0,
66 y: 0,
67 config: { mass: 1, tension: 170, friction: 26 }
68 }));
69
70 const handleMouseMove = (e) => {
71 const rect = e.currentTarget.getBoundingClientRect();
72 const x = e.clientX - rect.left - rect.width / 2;
73 const y = e.clientY - rect.top - rect.height / 2;
74 api2.start({ x: x * 0.3, y: y * 0.3 });
75 };
76
77 const handleMouseLeave = () => {
78 api2.start({ x: 0, y: 0 });
79 };
80
81 return (
82 <div>
83 {/* Basic spring */}
84 <animated.div style={basicSpring} className="spring-box">
85 <h3>Basic Spring Animation</h3>
86 </animated.div>
87
88 {/* Toggle animation */}
89 <button onClick={() => setOpen(!open)}>Toggle Animations</button>
90
91 <animated.div style={toggleSpring} className="toggle-content">
92 <p>This content springs in and out!</p>
93 </animated.div>
94
95 {/* Trail animation */}
96 <div className="trail-container">
97 {trail.map((style, index) => (
98 <animated.div
99 key={items[index].id}
100 style={style}
101 className="trail-item"
102 >
103 {items[index].text}
104 </animated.div>
105 ))}
106 </div>
107
108 {/* Chained animations */}
109 <div className="chain-container">
110 <animated.div style={spring1} className="chain-bar" />
111 <div className="chain-items">
112 {trail2.map((style, index) => (
113 <animated.div
114 key={index}
115 style={style}
116 className="chain-circle"
117 />
118 ))}
119 </div>
120 </div>
121
122 {/* Multiple springs */}
123 <button onClick={() => api.start({ scale: 0, rotation: 0 })}>
124 Reset Springs
125 </button>
126 <button onClick={() => api.start({ scale: 1, rotation: 360 })}>
127 Animate Springs
128 </button>
129
130 <div className="springs-container">
131 {springs.map((style, index) => (
132 <animated.div
133 key={index}
134 style={{
135 ...style,
136 transform: style.scale.to(s => `scale(${s}) rotate(${style.rotation}deg)`)
137 }}
138 className="spring-item"
139 />
140 ))}
141 </div>
142
143 {/* Interactive spring */}
144 <animated.div
145 onMouseMove={handleMouseMove}
146 onMouseLeave={handleMouseLeave}
147 style={{
148 transform: x.to((x, y) => `translate3d(${x}px, ${y}px, 0)`)
149 }}
150 className="interactive-spring"
151 >
152 <p>Move your mouse over me!</p>
153 </animated.div>
154
155 {/* Parallax effect */}
156 <div className="parallax-container">
157 {[0.5, 0.7, 1, 1.3].map((factor, i) => (
158 <animated.div
159 key={i}
160 style={{
161 transform: y.to(y => `translateY(${y * factor}px)`)
162 }}
163 className={`parallax-layer layer-${i}`}
164 />
165 ))}
166 </div>
167 </div>
168 );
169};
170
171// Custom hook for scroll-based spring
172const useScrollSpring = () => {
173 const [{ y }, api] = useSpring(() => ({ y: 0 }));
174
175 useEffect(() => {
176 const handleScroll = () => {
177 api.start({ y: window.scrollY });
178 };
179
180 window.addEventListener('scroll', handleScroll);
181 return () => window.removeEventListener('scroll', handleScroll);
182 }, [api]);
183
184 return y;
185};

Best Practices

  • Use spring physics for natural animations
  • Combine multiple springs for complex effects
  • React Spring works great with gestures
  • Use trails for staggered animations
  • Chain animations for orchestrated sequences

Performance & Best Practices

Optimizing animations for smooth 60fps performance

Performance is crucial for animations. Janky animations can make your app feel broken and frustrate users. This section covers techniques to ensure your animations run at a smooth 60 FPS.

Performance Guidelines:

  • Only animate transform and opacity (GPU-accelerated)
  • Use will-change sparingly and remove after animation
  • Batch DOM reads and writes
  • Use CSS containment for complex animations
  • Profile with Chrome DevTools Performance tab

Common Performance Pitfalls:

  • Animating width/height instead of transform: scale()
  • Too many simultaneous animations
  • Animating during scroll without throttling
  • Not respecting user's reduced motion preference
  • Large animated images without optimization

Implementation Example

1// Performance-optimized animations
2import { useState, useCallback, memo, useRef, useLayoutEffect } from 'react';
3import { motion } from 'framer-motion';
4
5// 1. Use CSS transforms instead of layout properties
6const OptimizedBox = memo(({ isActive }) => {
7 return (
8 <div
9 className="box"
10 style={{
11 // Good: Using transform
12 transform: `translateX(${isActive ? 100 : 0}px)`,
13 // Bad: Avoid animating left/top
14 // left: isActive ? 100 : 0
15 }}
16 />
17 );
18});
19
20// 2. Use will-change sparingly
21const WillChangeExample = () => {
22 const [isAnimating, setIsAnimating] = useState(false);
23
24 return (
25 <div
26 className="animated-element"
27 style={{
28 willChange: isAnimating ? 'transform' : 'auto'
29 }}
30 onMouseEnter={() => setIsAnimating(true)}
31 onMouseLeave={() => setIsAnimating(false)}
32 />
33 );
34};
35
36// 3. Debounce rapid animations
37const useDebounceAnimation = (value, delay) => {
38 const [debouncedValue, setDebouncedValue] = useState(value);
39
40 useEffect(() => {
41 const timer = setTimeout(() => {
42 setDebouncedValue(value);
43 }, delay);
44
45 return () => clearTimeout(timer);
46 }, [value, delay]);
47
48 return debouncedValue;
49};
50
51// 4. Use RAF for smooth animations
52const useRAF = (callback) => {
53 const requestRef = useRef();
54 const previousTimeRef = useRef();
55
56 const animate = useCallback((time) => {
57 if (previousTimeRef.current !== undefined) {
58 const deltaTime = time - previousTimeRef.current;
59 callback(deltaTime);
60 }
61 previousTimeRef.current = time;
62 requestRef.current = requestAnimationFrame(animate);
63 }, [callback]);
64
65 useEffect(() => {
66 requestRef.current = requestAnimationFrame(animate);
67 return () => cancelAnimationFrame(requestRef.current);
68 }, [animate]);
69};
70
71// 5. Optimize list animations
72const OptimizedList = memo(({ items }) => {
73 const itemRefs = useRef(new Map());
74
75 useLayoutEffect(() => {
76 const animations = [];
77
78 itemRefs.current.forEach((ref, index) => {
79 if (ref) {
80 animations.push(
81 ref.animate([
82 { opacity: 0, transform: 'translateY(20px)' },
83 { opacity: 1, transform: 'translateY(0)' }
84 ], {
85 duration: 300,
86 delay: index * 50,
87 easing: 'ease-out',
88 fill: 'both'
89 })
90 );
91 }
92 });
93
94 return () => {
95 animations.forEach(animation => animation.cancel());
96 };
97 }, [items]);
98
99 return (
100 <ul>
101 {items.map((item, index) => (
102 <li
103 key={item.id}
104 ref={ref => itemRefs.current.set(index, ref)}
105 >
106 {item.text}
107 </li>
108 ))}
109 </ul>
110 );
111});
112
113// 6. GPU-accelerated animations
114const GPUAcceleratedAnimation = () => {
115 return (
116 <motion.div
117 initial={{ opacity: 0, scale: 0.5 }}
118 animate={{ opacity: 1, scale: 1 }}
119 transition={{ duration: 0.5 }}
120 style={{
121 // Force GPU acceleration
122 transform: 'translateZ(0)',
123 // Or use will-change
124 willChange: 'transform'
125 }}
126 />
127 );
128};
129
130// 7. Reduce motion for accessibility
131const useReducedMotion = () => {
132 const [prefersReducedMotion, setPrefersReducedMotion] = useState(false);
133
134 useEffect(() => {
135 const mediaQuery = window.matchMedia('(prefers-reduced-motion: reduce)');
136 setPrefersReducedMotion(mediaQuery.matches);
137
138 const handleChange = (e) => setPrefersReducedMotion(e.matches);
139 mediaQuery.addEventListener('change', handleChange);
140
141 return () => mediaQuery.removeEventListener('change', handleChange);
142 }, []);
143
144 return prefersReducedMotion;
145};
146
147// Usage
148const AccessibleAnimation = () => {
149 const prefersReducedMotion = useReducedMotion();
150
151 return (
152 <motion.div
153 animate={{ x: prefersReducedMotion ? 0 : 100 }}
154 transition={{ duration: prefersReducedMotion ? 0 : 0.5 }}
155 >
156 Respects user preferences
157 </motion.div>
158 );
159};
160
161// 8. Virtualized animations for large lists
162const VirtualizedAnimatedList = ({ items, itemHeight = 50, containerHeight = 400 }) => {
163 const [scrollTop, setScrollTop] = useState(0);
164 const startIndex = Math.floor(scrollTop / itemHeight);
165 const endIndex = Math.min(
166 startIndex + Math.ceil(containerHeight / itemHeight) + 1,
167 items.length
168 );
169
170 const visibleItems = items.slice(startIndex, endIndex);
171
172 return (
173 <div
174 style={{ height: containerHeight, overflow: 'auto' }}
175 onScroll={(e) => setScrollTop(e.target.scrollTop)}
176 >
177 <div style={{ height: items.length * itemHeight, position: 'relative' }}>
178 {visibleItems.map((item, index) => (
179 <motion.div
180 key={item.id}
181 initial={{ opacity: 0, x: -20 }}
182 animate={{ opacity: 1, x: 0 }}
183 exit={{ opacity: 0, x: 20 }}
184 style={{
185 position: 'absolute',
186 top: (startIndex + index) * itemHeight,
187 height: itemHeight
188 }}
189 >
190 {item.content}
191 </motion.div>
192 ))}
193 </div>
194 </div>
195 );
196};

Best Practices

  • Animate transform and opacity for best performance
  • Use GPU acceleration with translateZ(0)
  • Implement reduced motion for accessibility
  • Virtualize long animated lists
  • Profile animations with Chrome DevTools

Advertisement Space - bottom-animations

Google AdSense: horizontal