Introduction
In modern web applications, smooth animations and transitions significantly enhance the user experience. React applications, especially single-page applications (SPAs), can leverage route transitions to make navigation feel more fluid and engaging.
Adding animations to route changes improves the perception of performance and provides visual continuity when switching between pages. Libraries like Framer Motion or CSS transitions make implementing these animations straightforward.
In this post, we will cover:
- Adding page transitions with Framer Motion or CSS
- Example transitions: fade-in/out and slide effects
- Combining route changes with smooth animations
- Optimizing transitions for nested and dynamic routes
Using Framer Motion for Route Animations
Framer Motion is a powerful React library for animations. It supports motion components, variants, and animation hooks, which make route transitions smooth and declarative.
Installing Framer Motion
npm install framer-motion
- Provides
<motion.div>and other animated components. - Supports simple to complex animations.
Basic Fade-In/Fade-Out Transition
A common effect is a fade-in/fade-out when a new page is rendered.
Example
import React from 'react';
import { motion } from 'framer-motion';
function Page({ children }) {
return (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.5 }}
>
{children}
</motion.div>
);
}
export default Page;
initial: Starting state when component mountsanimate: Target state for the animationexit: State when component unmountstransition: Duration and easing
Integrating Framer Motion with React Router
React Router provides route changes, but by default components mount/unmount immediately. Combining it with AnimatePresence from Framer Motion allows animations during route transitions.
Setup Example
import React from 'react';
import { BrowserRouter as Router, Routes, Route, useLocation } from 'react-router-dom';
import { AnimatePresence } from 'framer-motion';
import Home from './Home';
import About from './About';
import Contact from './Contact';
import Page from './Page';
function AnimatedRoutes() {
const location = useLocation();
return (
<AnimatePresence exitBeforeEnter>
<Routes location={location} key={location.pathname}>
<Route path="/" element={<Page><Home /></Page>} />
<Route path="/about" element={<Page><About /></Page>} />
<Route path="/contact" element={<Page><Contact /></Page>} />
</Routes>
</AnimatePresence>
);
}
function App() {
return (
<Router>
<AnimatedRoutes />
</Router>
);
}
export default App;
Key Points:
AnimatePresenceenables exit animations for unmounted components.key={location.pathname}ensures each route triggers a new animation.- Wrapping pages in
<Page>applies consistent animation styles.
Slide Transitions Between Routes
Slide transitions create a sense of movement when navigating between pages.
Slide Example
function PageSlide({ children }) {
return (
<motion.div
initial={{ x: 300, opacity: 0 }}
animate={{ x: 0, opacity: 1 }}
exit={{ x: -300, opacity: 0 }}
transition={{ type: 'spring', stiffness: 100 }}
>
{children}
</motion.div>
);
}
xrepresents horizontal movement.springtransition adds natural bounce.- Customize
stiffnessanddampingfor smoother motion.
Combining Route Changes with Animations
To apply consistent animations for multiple routes:
- Wrap all route components in an animated wrapper.
- Use unique keys for each route.
- Apply initial, animate, and exit states.
- Configure transitions for timing and easing.
Example
<AnimatePresence exitBeforeEnter>
<Routes location={location} key={location.pathname}>
<Route path="/" element={<PageSlide><Home /></PageSlide>} />
<Route path="/about" element={<PageSlide><About /></PageSlide>} />
<Route path="/contact" element={<PageSlide><Contact /></PageSlide>} />
</Routes>
</AnimatePresence>
- Navigation triggers smooth entry and exit animations.
- Enhances perceived speed and user experience.
Animating Nested Routes
Nested routes require careful handling to animate both parent and child components.
Example
function Dashboard() {
return (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.3 }}
>
<h1>Dashboard</h1>
<Routes>
<Route path="profile" element={<Profile />} />
<Route path="settings" element={<Settings />} />
</Routes>
</motion.div>
);
}
- Parent page animation triggers first.
- Nested routes can animate individually.
- Combine
AnimatePresenceat parent level for smoother transitions.
Dynamic Route Transitions
Dynamic routes, such as /users/:id, can also be animated.
<Route
path="/users/:id"
element={
<PageSlide>
<UserProfile />
</PageSlide>
}
/>
- Each dynamic route is treated as a unique key using
location.pathname. - Allows smooth transitions even when navigating between user profiles.
CSS-Based Transitions
While Framer Motion is powerful, CSS transitions can also achieve basic animations.
Example: Fade with CSS
.page-enter {
opacity: 0;
}
.page-enter-active {
opacity: 1;
transition: opacity 0.5s ease-in;
}
.page-exit {
opacity: 1;
}
.page-exit-active {
opacity: 0;
transition: opacity 0.5s ease-out;
}
import { CSSTransition, TransitionGroup } from 'react-transition-group';
<TransitionGroup>
<CSSTransition key={location.key} classNames="page" timeout={500}>
<Routes location={location}>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
</CSSTransition>
</TransitionGroup>
CSSTransitionintegrates CSS animations with route changes.- Works well for simple transitions like fade or slide.
Optimizing Transitions
- Avoid heavy animations on large components to prevent lag.
- Lazy load components for faster initial load.
- Use
exitBeforeEnterin Framer Motion to ensure proper exit animations. - Animate only visible components to reduce rendering cost.
- Combine with code-splitting for nested and dynamic routes.
Example: Complete Animated App
import React from 'react';
import { BrowserRouter as Router, Routes, Route, useLocation } from 'react-router-dom';
import { AnimatePresence, motion } from 'framer-motion';
import Home from './Home';
import About from './About';
import Contact from './Contact';
function PageSlide({ children }) {
return (
<motion.div
initial={{ x: 300, opacity: 0 }}
animate={{ x: 0, opacity: 1 }}
exit={{ x: -300, opacity: 0 }}
transition={{ type: 'spring', stiffness: 100 }}
>
{children}
</motion.div>
);
}
function AnimatedRoutes() {
const location = useLocation();
return (
<AnimatePresence exitBeforeEnter>
<Routes location={location} key={location.pathname}>
<Route path="/" element={<PageSlide><Home /></PageSlide>} />
<Route path="/about" element={<PageSlide><About /></PageSlide>} />
<Route path="/contact" element={<PageSlide><Contact /></PageSlide>} />
</Routes>
</AnimatePresence>
);
}
function App() {
return (
<Router>
<AnimatedRoutes />
</Router>
);
}
export default App;
- Smooth sliding transitions between Home, About, and Contact pages.
- Uses Framer Motion for declarative animations.
- Works for both top-level and nested routes.
Best Practices
- Use consistent animations for all pages to maintain UX coherence.
- Prefer declarative animations using Framer Motion over manual CSS manipulation.
- Animate only what is necessary to avoid performance issues.
- Combine lazy loading with transitions for optimized rendering.
- Test on different devices to ensure smooth animations.
- Use route keys to trigger exit/enter animations properly.
- Keep transitions subtle to avoid distraction.
Leave a Reply