You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Next.js项目中切换URL时实现右侧内容动画且避免左侧面板重渲染

嘿,这个场景我之前做过,刚好能给你一套可行的解决方案,咱们一步步来:

解决方案(覆盖Next.js两种路由系统)

核心思路是利用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/navigationuseRouter

// 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

火山引擎 最新活动