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

Material-UI Tabs横向滚动失效问题求助

解决Material-UI可滚动Tabs失效的问题

我仔细看了你的代码,发现几个导致滚动功能失效的核心问题,咱们一步步来修复:

问题分析

  1. 重复的DOM ID与索引:你代码里多个Tab和TabPanel使用了重复的index值(比如多个Tab用a11yProps(4)),这会导致DOM元素ID重复,破坏Material-UI Tabs组件的内部逻辑,包括滚动功能的触发机制。
  2. Tab与TabPanel数量不匹配:你有10个Tab,但TabPanel的索引重复,没有为每个Tab对应唯一的面板,这也会干扰组件的正常交互。
  3. 动态Tab场景适配缺失:你需要动态数量的Tabs,但当前代码是硬编码的,容易出现索引混乱,也不利于后续扩展。

修复步骤

1. 重构为动态Tab生成逻辑

先把Tab数据抽成数组,这样可以灵活控制数量,同时保证每个Tab有唯一的索引:

// 定义动态Tab数据,可根据业务需求动态生成/修改
const tabData = [
  { label: "Item One", icon: <PhoneIcon /> },
  { label: "Item Two", icon: <FavoriteIcon /> },
  { label: "Item Three", icon: <PersonPinIcon /> },
  { label: "Item Four", icon: <HelpIcon /> },
  { label: "Item Five", icon: <ShoppingBasket /> },
  { label: "Item Six", icon: <ThumbDown /> },
  { label: "Item Seven", icon: <ThumbUp /> },
  { label: "Item Eight", icon: <ShoppingBasket /> },
  { label: "Item Nine", icon: <ThumbDown /> },
  { label: "Item Ten", icon: <ThumbUp /> },
];

2. 修正Tab和TabPanel的索引与ID

确保每个Tab和对应的TabPanel使用唯一的索引,避免ID重复:

// 动态生成Tabs,每个Tab对应唯一索引
<Tabs
  value={value}
  onChange={handleChange}
  variant="scrollable"
  scrollButtons="on"
  indicatorColor="primary"
  textColor="primary"
  aria-label="scrollable force tabs example"
  // 添加样式确保滚动容器不受AppBar的flex布局限制
  sx={{ flexGrow: 0 }}
>
  {tabData.map((tab, index) => (
    <Tab
      key={index}
      label={tab.label}
      icon={tab.icon}
      {...a11yProps(index)}
    />
  ))}
</Tabs>

// 对应生成唯一的TabPanel
{tabData.map((tab, index) => (
  <TabPanel key={index} value={value} index={index}>
    {tab.label}
  </TabPanel>
))}

3. 调整样式确保滚动容器正常工作

默认的AppBar会给Tabs添加flexGrow:1,导致Tabs容器被拉伸填满AppBar宽度,无法触发横向滚动。添加sx={{ flexGrow: 0 }}可以让Tabs保持自身宽度,超出容器时自动激活滚动功能。

完整修复后的代码

import React from 'react';
import PropTypes from 'prop-types';
import { makeStyles } from '@material-ui/core/styles';
import AppBar from '@material-ui/core/AppBar';
import Tabs from '@material-ui/core/Tabs';
import Tab from '@material-ui/core/Tab';
import PhoneIcon from '@material-ui/icons/Phone';
import FavoriteIcon from '@material-ui/icons/Favorite';
import PersonPinIcon from '@material-ui/icons/PersonPin';
import HelpIcon from '@material-ui/icons/Help';
import ShoppingBasket from '@material-ui/icons/ShoppingBasket';
import ThumbDown from '@material-ui/icons/ThumbDown';
import ThumbUp from '@material-ui/icons/ThumbUp';
import Typography from '@material-ui/core/Typography';
import Box from '@material-ui/core/Box';

function TabPanel(props) {
  const { children, value, index, ...other } = props;
  return (
    <div
      role="tabpanel"
      hidden={value !== index}
      id={`scrollable-force-tabpanel-${index}`}
      aria-labelledby={`scrollable-force-tab-${index}`}
      {...other}
    >
      {value === index && (
        <Box p={3}>
          <Typography>{children}</Typography>
        </Box>
      )}
    </div>
  );
}

TabPanel.propTypes = {
  children: PropTypes.node,
  index: PropTypes.any.isRequired,
  value: PropTypes.any.isRequired,
};

function a11yProps(index) {
  return {
    id: `scrollable-force-tab-${index}`,
    'aria-controls': `scrollable-force-tabpanel-${index}`,
  };
}

const useStyles = makeStyles((theme) => ({
  root: {
    flexGrow: 1,
    width: '100%',
    backgroundColor: theme.palette.background.paper,
  },
}));

export default function TabsButton() {
  const classes = useStyles();
  const [value, setValue] = React.useState(0);

  // 动态Tab数据,支持后续业务动态修改
  const tabData = [
    { label: "Item One", icon: <PhoneIcon /> },
    { label: "Item Two", icon: <FavoriteIcon /> },
    { label: "Item Three", icon: <PersonPinIcon /> },
    { label: "Item Four", icon: <HelpIcon /> },
    { label: "Item Five", icon: <ShoppingBasket /> },
    { label: "Item Six", icon: <ThumbDown /> },
    { label: "Item Seven", icon: <ThumbUp /> },
    { label: "Item Eight", icon: <ShoppingBasket /> },
    { label: "Item Nine", icon: <ThumbDown /> },
    { label: "Item Ten", icon: <ThumbUp /> },
  ];

  const handleChange = (event, newValue) => {
    setValue(newValue);
  };

  return (
    <div className={classes.root}>
      <AppBar position="static" color="default">
        <Tabs
          value={value}
          onChange={handleChange}
          variant="scrollable"
          scrollButtons="on"
          indicatorColor="primary"
          textColor="primary"
          aria-label="scrollable force tabs example"
          // 确保Tabs容器保持自身宽度,触发滚动
          sx={{ flexGrow: 0 }}
        >
          {tabData.map((tab, index) => (
            <Tab
              key={index}
              label={tab.label}
              icon={tab.icon}
              {...a11yProps(index)}
            />
          ))}
        </Tabs>
      </AppBar>
      {tabData.map((tab, index) => (
        <TabPanel key={index} value={value} index={index}>
          {tab.label}
        </TabPanel>
      ))}
    </div>
  );
}

额外说明

  • 如果需要从外部动态传入Tab数据,只需要把tabData改成组件的props即可,比如const { tabData } = props;。
  • 确保所有Tab和TabPanel的key和index都是唯一的,这是React列表渲染的基本要求,也能避免Material-UI组件的逻辑冲突。

内容的提问来源于stack exchange,提问作者sachin murali

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.07 19:37:42