Smooth Page Transitions: Framer Motion and View Transitions API
Imagine: a user clicks on a product in the catalog, sees a white flash, and a second later—a new page. This not only annoys but also worsens Core Web Vitals—LCP and CLS. As a result, the site loses conversions: according to our projects, smooth transitions increase retention by 15–25%. We work with projects of any scale—from landing pages to marketplaces with 10,000+ pages. Smooth transitions are especially important for e-commerce, where 80% of interactions are catalog navigation. Implementing animations pays off in 1–2 months due to conversion growth. Our team implements transitions that reduce perceived load time by 30–40% and improve user experience. Order an audit of your current transitions—it takes one day.
Three key problems solved by transition animations
Transition animations solve three tasks: spatial context (the user understands where they navigated), perception of speed (mask load time), and preventing flickering (no white flashes). Typical implementation mistakes: identical transitions forward/backward, ignoring accessibility, and animations longer than 300 ms. Optimal duration is 200–250 ms.
How to choose between framer-motion and View Transitions API?
The choice depends on the stack and required customization. Framer-motion is more flexible but requires additional code. View Transitions API is a native approach, especially convenient for MPA and Next.js. For complex scenarios (directed transitions, shared elements), we recommend framer-motion: it gives full control. Contact us for a consultation—we will help choose the optimal solution.
| Stack | Recommended approach |
|---|---|
| React + React Router | framer-motion + AnimatePresence |
| Next.js App Router | View Transitions API or framer-motion |
| Vue / Nuxt | <Transition> + <TransitionGroup> |
| Astro | View Transitions API natively |
| Multi-page site (MPA) | View Transitions API |
Implementing directed transitions with framer-motion
For hierarchical navigation (catalog → product → cart), the transition should be directed: forward—slide right, backward—slide left. In framer-motion, this is implemented through custom variants with navigation history tracking. Step by step:
- Create a
useNavigationDirectionhook that tracks history and returns 1 (forward) or -1 (backward). - Define a variants object with functions that accept direction.
- Use
<AnimatePresence mode="wait">and wrap the page content in<motion.div key={location.pathname} custom={direction} variants={variants} initial="initial" animate="animate" exit="exit" />.
Code example:
const useNavigationDirection = () => { const [direction, setDirection] = useState(0); const location = useLocation(); const prevLocation = useRef(location); const navHistory = useRef<string[]>([location.pathname]); useEffect(() => { const currentPath = location.pathname; const history = navHistory.current; const prevIndex = history.lastIndexOf(prevLocation.current.pathname); const currentIndex = history.indexOf(currentPath); if (currentIndex > prevIndex) setDirection(1); // forward else setDirection(-1); // backward if (currentIndex === -1) { navHistory.current = [...history, currentPath]; } prevLocation.current = location; }, [location]); return direction; }; const variants = { initial: (dir: number) => ({ x: dir > 0 ? '100%' : '-100%', opacity: 0 }), animate: { x: 0, opacity: 1 }, exit: (dir: number) => ({ x: dir > 0 ? '-100%' : '100%', opacity: 0 }), }; What is the View Transitions API and how to use it?
The View Transitions API is a native browser mechanism for smooth transitions between pages. Supported in Chrome 111+ and Safari 18+. For MPA and Next.js, minimal code is required:
async function navigateTo(url) { if (!document.startViewTransition) { window.location.href = url; return; } const transition = document.startViewTransition(async () => { const html = await fetch(url).then(r => r.text()); const doc = new DOMParser().parseFromString(html, 'text/html'); document.querySelector('main').replaceWith(doc.querySelector('main')); history.pushState({}, '', url); }); await transition.ready; } CSS for controlling animation:
@keyframes slide-from-right { from { transform: translateX(100%); } } @keyframes slide-to-left { to { transform: translateX(-100%); } } ::view-transition-old(root) { animation: 250ms ease slide-to-left; } ::view-transition-new(root) { animation: 250ms ease slide-from-right; } .product-image { view-transition-name: product-hero; } Shared element transitions—hero animations where a specific element (product card) smoothly "transforms" into the hero image on the product page. This is the most impressive feature of the View Transitions API.
Skeleton screens and animation accessibility
During transitions, data often loads asynchronously. A spinner shows "loading"—a skeleton screen shows the page structure:
const ProductSkeleton: React.FC = () => ( <motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} className="skeleton-wrapper" > <div className="skeleton skeleton--image" /> <div className="skeleton skeleton--title" /> <div className="skeleton skeleton--text" /> <div className="skeleton skeleton--text skeleton--short" /> </motion.div> ); .skeleton { background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%); background-size: 200% 100%; animation: shimmer 1.5s infinite; border-radius: 4px; } @keyframes shimmer { 0% { background-position: 200% 0; } 100% { background-position: -200% 0; } } Animation accessibility is mandatory: use prefers-reduced-motion: reduce. In CSS, disable animations for ::view-transition-group; in framer-motion, use the useReducedMotion hook with duration: 0. During fast navigation, animations should be interruptible—framer-motion does this automatically via AnimatePresence, and for custom animations, simply reduce duration to 150–200 ms.
What is included in the work
- Audit of current navigation and performance (Core Web Vitals, request waterfall).
- Selection of approach: framer-motion, View Transitions API, or a combination.
- Implementation of animations with accessibility (prefers-reduced-motion).
- Creation of skeleton screens for all templates.
- Integration with the router (React Router, Next.js, Vue Router).
- Optimization for Core Web Vitals (LCP < 2.5s, CLS < 0.1).
- Code documentation and team training.
- Animation warranty—6 months after deployment.
Process and timelines
Stages: analytics (audit current navigation) → design (choose approach, prototype) → implementation → testing Core Web Vitals and accessibility → deployment. Approximate timelines:
| Task | Time |
|---|---|
| Basic fade/slide transitions (framer-motion) | 0.5 day |
| Directed forward/backward transitions | 1 day |
| View Transitions API + shared elements | 1–2 days |
| Skeleton screens for 3–5 templates | 1 day |
Get a consultation for your project—our team has many years of web development experience and over 40 implemented projects with animations.







