You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于Next.js与Chakra UI实现多级Tab及对应功能

实现带平滑过渡的双层Tab组件(Next.js + Chakra UI)

需求实现方案

针对你需要的主分类切换子分类平滑过渡、子分类点击内容滚动聚焦的需求,以下是修改后的完整代码:

import { useState, useRef } from 'react';
import { Tabs, TabList, TabPanel, TabPanels, Box, Button, Tab } from '@chakra-ui/react';
import { motion, AnimatePresence } from 'framer-motion';

const MotionTab = motion(Tab);
const MotionTabPanel = motion(TabPanel);

const categories = [
  { label: "パスタ", tooltip: "Main Pasta Menu", subcategories: ["OIL", "MEAT", "JAPANESE TASTE", "TOMATO", "ROASTED"] },
  { label: "肉料理", tooltip: "Meat Dishes", subcategories: ["GRILLED", "FRIED", "ROASTED"] },
  { label: "ドルチェ", tooltip: "Desserts", subcategories: ["CAKES", "PIES", "ICE CREAM"] },
  { label: "キッズ", tooltip: "Kids Menu", subcategories: ["MEALS", "SNACKS"] },
  { label: "プレ", tooltip: "Premium Menu", subcategories: ["SPECIAL DISH", "WINE PAIRINGS"] },
  { label: "肉料理", tooltip: "Meat Dishes", subcategories: ["GRILLED", "FRIED", "ROASTED"] },
  { label: "ドルチェ", tooltip: "Desserts", subcategories: ["CAKES", "PIES", "ICE CREAM"] },
  { label: "キッズ", tooltip: "Kids Menu", subcategories: ["MEALS", "SNACKS"] },
];

// 模拟内容数据,可替换为实际业务内容
const contentData = categories.map(category => 
  category.subcategories.map(sub => `这是${category.label}分类下${sub}的详情内容...`.repeat(10))
);

export const TabMenu = () => {
  const [activeSubcategory, setActiveSubcategory] = useState({
    tabIndex: 0, 
    subIndex: null, 
  });
  const contentRefs = useRef<(HTMLDivElement | null)[]>([]);

  // 子分类点击后触发内容滚动聚焦
  const handleSubcategoryClick = (tabIndex: number, subIndex: number) => {
    setActiveSubcategory({ tabIndex, subIndex });
    setTimeout(() => {
      contentRefs.current[tabIndex * 10 + subIndex]?.scrollIntoView({
        behavior: 'smooth',
        block: 'start'
      });
    }, 100);
  };

  return (
    <Tabs onChange={(index) => setActiveSubcategory({ tabIndex: index, subIndex: null })}>
      {/* 主分类Tab栏 */}
      <Box overflowX="auto" whiteSpace="nowrap" css={{
        "&::-webkit-scrollbar": { display: "none" }
      }}>
        <TabList display="inline-flex" gap={0}>
          {categories.map((category, index) => (         
            <MotionTab
              as="div"
              key={index}
              _selected={{
                bg: "brand.primary", 
                color: "white",
                position: "relative",          
                "::after": {
                  content: `""`,
                  position: "absolute",
                  bottom: "-6px", 
                  left: "50%",
                  transform: "translateX(-50%)",
                  width: "0",
                  height: "0",
                  borderLeft: "6px solid transparent",
                  borderRight: "6px solid transparent",
                  borderTop: "8px solid brown.500", 
                },
              }}
              className='bold-small'
              color="mono.primary"
              whileHover={{ scale: 1.02 }}
              whileTap={{ scale: 0.98 }}
            >
              {category.label}
            </MotionTab>          
          ))}
        </TabList>
      </Box>

      {/* 子分类面板(带平滑过渡动画) */}
      <AnimatePresence mode="wait">
        <TabPanels borderBottom="1px solid" borderColor="mono.divider" key={activeSubcategory.tabIndex}>
          <MotionTabPanel
            key={activeSubcategory.tabIndex}
            p="0px"
            initial={{ x: "100%", opacity: 0 }}
            animate={{ x: 0, opacity: 1 }}
            exit={{ x: "-100%", opacity: 0 }}
            transition={{ duration: 0.3, ease: "easeInOut" }}
          >
            <Box
              overflowX="auto" 
              whiteSpace="nowrap" 
              p="8px" 
              gap="8px" 
              css={{
                "&::-webkit-scrollbar": { display: "none" }
              }}
            >
              {categories[activeSubcategory.tabIndex].subcategories.map((subcategory, subIndex) => (
                <Button
                  key={subIndex}
                  borderRadius="full"
                  bg={
                    activeSubcategory.subIndex === subIndex
                      ? "brand.primary"
                      : "mono.backGround"
                  }
                  color={
                    activeSubcategory.subIndex === subIndex
                      ? "white"
                      : "mono.primary"
                  }              
                  size="md"
                  mr="8px"
                  px="18px"
                  py="8px"
                  onClick={() => handleSubcategoryClick(activeSubcategory.tabIndex, subIndex)}
                  whileHover={{ scale: 1.05 }}
                  whileTap={{ scale: 0.95 }}
                >
                  {subcategory}
                </Button>
              ))}
            </Box>
          </MotionTabPanel>
        </TabPanels>
      </AnimatePresence>

      {/* 内容展示区域 */}
      <Box mt={4} overflowY="auto" maxHeight="400px" css={{
        "&::-webkit-scrollbar": { width: "6px" },
        "&::-webkit-scrollbar-thumb": { bg: "mono.divider", borderRadius: "3px" }
      }}>
        {contentData[activeSubcategory.tabIndex].map((content, subIndex) => (
          <div 
            key={subIndex} 
            ref={el => contentRefs.current[activeSubcategory.tabIndex * 10 + subIndex] = el}
            p={4}
            mb={2}
            borderRadius="md"
            bg={activeSubcategory.subIndex === subIndex ? "mono.light" : "transparent"}
          >
            <h3 style={{ marginBottom: '8px', fontWeight: 'bold' }}>
              {categories[activeSubcategory.tabIndex].subcategories[subIndex]}
            </h3>
            <p>{content}</p>
          </div>
        ))}
      </Box>
    </Tabs>
  );
};

核心实现细节

  1. 子分类平滑过渡

    • 用AnimatePresence包裹TabPanels,配合motion(TabPanel)实现切换动画
    • 配置初始从右侧划入、退出向左划出的位移+透明度动画,过渡时长0.3秒
  2. 内容滚动聚焦

    • 通过useRef存储每个内容项的DOM节点
    • 点击子分类后调用scrollIntoView,实现平滑滚动到目标内容
  3. 交互体验优化

    • 为主Tab和子分类按钮添加hover、tap缩放动画
    • 选中的子分类内容添加背景高亮,增强视觉识别

内容的提问来源于stack exchange,提问作者Shiba Y.

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 18:35:59