Next.js项目中切换URL时实现右侧内容动画且避免左侧面板重渲染
嘿,这个场景我之前做过,刚好能给你一套可行的解决方案,咱们一步步来:
核心思路是利用Next.js的动态路由来匹配你要的URL结构,把左侧面板放在全局布局里避免重渲染,用官方路由API更新URL,再配合动画库实现右侧内容的切换过渡。
Pages Router(Next.js 12及以下)
1. 搭建动态路由结构
先创建对应层级的动态路由文件,让URL能匹配localhost/users/[username]/categories/[category]/items:
pages/ ├─ users/ │ ├─ [username]/ │ │ ├─ categories/ │ │ │ ├─ [category]/ │ │ │ │ ├─ items/ │ │ │ │ │ └─ index.js
这个index.js就是最终展示物品列表的页面,同理你可以在[username]/categories下创建页面来展示该用户的分类列表。
2. 分离左侧面板到全局布局
在_app.js里引入自定义布局组件,把左侧面板固定在布局中,这样路由切换时只有右侧内容更新,左侧不会重渲染:
// pages/_app.js import Layout from '../components/Layout'; function MyApp({ Component, pageProps }) { return ( <Layout> <Component {...pageProps} /> </Layout> ); } export default MyApp;
// components/Layout.js export default function Layout({ children }) { return ( <div className="flex h-screen"> {/* 左侧面板 - 路由切换时不会卸载 */} <div className="w-64 bg-gray-100 p-4"> {/* 你的左侧面板内容 */} </div> {/* 右侧内容区域 - 路由切换时更新 */} <div className="flex-1 overflow-auto p-4"> {children} </div> </div> ); }
3. 用useRouter更新URL
不要再手动切换组件了,用Next.js的useRouter来触发路由跳转,同时更新URL:
// 用户名选择组件 import { useRouter } from 'next/router'; export default function UserSelector() { const router = useRouter(); const handleSelect = (username) => { // 跳转到该用户的分类列表页面 router.push(`/users/${username}/categories`); }; return ( <select onChange={(e) => handleSelect(e.target.value)}> <option value="">选择用户名</option> <option value="alice">alice</option> <option value="bob">bob</option> </select> ); }
分类选择同理,点击分类后跳转到物品页面:
// 分类列表组件 import { useRouter } from 'next/router'; export default function CategoryList({ username, categories }) { const router = useRouter(); const handleCategoryClick = (category) => { router.push(`/users/${username}/categories/${category}/items`); }; return ( <div className="grid grid-cols-3 gap-2"> {categories.map(category => ( <div key={category} className="p-2 bg-blue-50 cursor-pointer" onClick={() => handleCategoryClick(category)} > {category} </div> ))} </div> ); }
4. 添加切换动画
用framer-motion实现平滑过渡动画,先安装依赖:
npm install framer-motion
然后在_app.js里用AnimatePresence包裹页面组件,给每个页面加唯一key(用当前URL):
// pages/_app.js import Layout from '../components/Layout'; import { AnimatePresence } from 'framer-motion'; import { useRouter } from 'next/router'; function MyApp({ Component, pageProps }) { const router = useRouter(); return ( <Layout> <AnimatePresence mode="wait"> <Component key={router.asPath} {...pageProps} /> </AnimatePresence> </Layout> ); } export default MyApp;
最后在每个页面组件里定义动画效果:
// pages/users/[username]/categories/[category]/items/index.js import { motion } from 'framer-motion'; export default function ItemsPage({ items }) { return ( <motion.div initial={{ opacity: 0, x: 20 }} animate={{ opacity: 1, x: 0 }} exit={{ opacity: 0, x: -20 }} transition={{ duration: 0.3 }} > <h2>物品列表</h2> <ul> {items.map(item => ( <li key={item.id}>{item.name}</li> ))} </ul> </motion.div> ); } // 用getServerSideProps获取对应数据 export async function getServerSideProps(context) { const { username, category } = context.params; // 这里替换成你的数据接口请求 const items = await fetch(`https://your-api.com/users/${username}/categories/${category}/items`).then(res => res.json()); return { props: { items } }; }
App Router(Next.js 13+)
1. 搭建动态路由结构
在app目录下创建对应层级的路由:
app/ ├─ users/ │ ├─ [username]/ │ │ ├─ categories/ │ │ │ ├─ [category]/ │ │ │ │ ├─ items/ │ │ │ │ │ └─ page.js
2. 左侧面板放在顶层布局
App Router的顶层layout.js会在所有路由切换时保持挂载,直接把左侧面板放在这里:
// app/layout.js import { AnimatePresence } from 'framer-motion'; import { usePathname } from 'next/navigation'; export default function RootLayout({ children }) { const pathname = usePathname(); return ( <html lang="en"> <body className="flex h-screen"> {/* 左侧面板 - 永久挂载 */} <div className="w-64 bg-gray-100 p-4">左侧面板内容</div> {/* 右侧内容区域 */} <div className="flex-1 overflow-auto p-4"> <AnimatePresence mode="wait"> <div key={pathname}>{children}</div> </AnimatePresence> </div> </body> </html> ); }
3. 用useRouter更新URL
App Router用next/navigation的useRouter:
// app/users/[username]/categories/page.js 'use client'; // 因为用了客户端钩子 import { useRouter } from 'next/navigation'; export default function CategoriesPage({ params }) { const { username } = params; const router = useRouter(); // 假设这里获取到了该用户的分类列表 const categories = ['电子设备', '图书', '服饰']; const handleCategoryClick = (category) => { router.push(`/users/${username}/categories/${category}/items`); }; return ( <div> <h2>{username}的分类</h2> <div className="grid grid-cols-3 gap-2"> {categories.map(cat => ( <div key={cat} className="p-2 bg-blue-50 cursor-pointer" onClick={() => handleCategoryClick(cat)} > {cat} </div> ))} </div> </div> ); }
4. 添加动画
和Pages Router类似,给页面组件加motion.div:
// app/users/[username]/categories/[category]/items/page.js 'use client'; import { motion } from 'framer-motion'; export default function ItemsPage({ params }) { const { username, category } = params; // 假设这里获取到了物品数据 const items = [{ id: 1, name: '手机' }, { id: 2, name: '耳机' }]; return ( <motion.div initial={{ opacity: 0, x: 20 }} animate={{ opacity: 1, x: 0 }} exit={{ opacity: 0, x: -20 }} transition={{ duration: 0.3 }} > <h2>{username}的{category}物品</h2> <ul> {items.map(item => ( <li key={item.id}>{item.name}</li> ))} </ul> </motion.div> ); }
这样下来,URL会严格按照你要的users/categories/items结构变化,左侧面板全程不会重渲染,右侧内容切换还有平滑的过渡动画,完美匹配你的需求~
内容的提问来源于stack exchange,提问作者CoderBC




