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

React MUI布局中汉堡按钮在大屏设备下无法切换侧边栏的问题解决

React MUI布局中汉堡按钮在大屏设备下无法切换侧边栏的问题解决

我完全懂你现在的困扰——你这套React + Material UI的布局里,汉堡按钮只能在小屏幕上切换侧边栏,到大屏设备时侧边栏就固定显示着,按钮完全没作用,想让它在所有屏幕尺寸下都有一致的切换行为对吧?别担心,咱们来搞定这个问题。

问题根源

原来的代码里,大屏设备用的是variant="permanent"的Drawer组件,而且直接把open属性设为true,这就导致它一直固定显示,完全不受侧边栏状态的控制;而汉堡按钮的点击事件只关联了小屏用的临时Drawer,自然在大屏上没效果。

解决方案思路

我们要让所有屏幕尺寸下的侧边栏都受同一个状态控制,把大屏的Drawer改成可受控的模式,同时调整样式适配侧边栏收起/展开的状态,最后让汉堡按钮的行为在全尺寸下统一。

修改后的完整代码

import * as React from "react";
import AppBar from "@mui/material/AppBar";
import Box from "@mui/material/Box";
import CssBaseline from "@mui/material/CssBaseline";
import Divider from "@mui/material/Divider";
import Drawer from "@mui/material/Drawer";
import IconButton from "@mui/material/IconButton";
import InboxIcon from "@mui/icons-material/MoveToInbox";
import List from "@mui/material/List";
import ListItem from "@mui/material/ListItem";
import ListItemButton from "@mui/material/ListItemButton";
import ListItemIcon from "@mui/material/ListItemIcon";
import ListItemText from "@mui/material/ListItemText";
import MailIcon from "@mui/icons-material/Mail";
import MenuIcon from "@mui/icons-material/Menu";
import Toolbar from "@mui/material/Toolbar";
import Typography from "@mui/material/Typography";

const drawerWidth = 240;

interface Props {
  window?: () => Window;
}

export default function ResponsiveDrawer(props: Props) {
  const { window } = props;
  // 用一个统一的状态控制所有屏幕下的侧边栏显示
  const [isDrawerOpen, setIsDrawerOpen] = React.useState(true);
  const [isClosing, setIsClosing] = React.useState(false);

  const handleDrawerClose = () => {
    setIsClosing(true);
    setIsDrawerOpen(false);
  };

  const handleDrawerTransitionEnd = () => {
    setIsClosing(false);
  };

  const handleDrawerToggle = () => {
    if (!isClosing) {
      setIsDrawerOpen(!isDrawerOpen);
    }
  };

  const drawer = (
    <div>
      <Toolbar />
      <Divider />
      <List>
        {["Inbox", "Starred", "Send email", "Drafts"].map((text, index) => (
          <ListItem key={text} disablePadding>
            <ListItemButton>
              <ListItemIcon>
                {index % 2 === 0 ? <InboxIcon /> : <MailIcon />}
              </ListItemIcon>
              <ListItemText primary={text} />
            </ListItemButton>
          </ListItem>
        ))}
      </List>
      <Divider />
      <List>
        {["All mail", "Trash", "Spam"].map((text, index) => (
          <ListItem key={text} disablePadding>
            <ListItemButton>
              <ListItemIcon>
                {index % 2 === 0 ? <InboxIcon /> : <MailIcon />}
              </ListItemIcon>
              <ListItemText primary={text} />
            </ListItemButton>
          </ListItem>
        ))}
      </List>
    </div>
  );

  const container = window !== undefined ? () => window().document.body : undefined;

  return (
    <Box sx={{ display: "flex" }}>
      <CssBaseline />
      <AppBar
        position="fixed"
        sx={{
          zIndex: (theme) => theme.zIndex.drawer + 1,
          // 侧边栏收起时,AppBar占满宽度
          width: isDrawerOpen ? { sm: `calc(100% - ${drawerWidth}px)` } : "100%"
        }}
      >
        <Toolbar>
          <IconButton
            color="inherit"
            aria-label="open drawer"
            edge="start"
            onClick={handleDrawerToggle}
            sx={{ mr: 2 }}
          >
            <MenuIcon />
          </IconButton>
          <Typography variant="h6" noWrap component="div">
            Responsive drawer
          </Typography>
        </Toolbar>
      </AppBar>
      <Box
        component="nav"
        sx={{
          flexShrink: { sm: 0 },
          width: isDrawerOpen ? { sm: drawerWidth } : { sm: 0 }
        }}
        aria-label="mailbox folders"
      >
        {/* 小屏用临时Drawer */}
        <Drawer
          container={container}
          variant="temporary"
          open={isDrawerOpen}
          onTransitionEnd={handleDrawerTransitionEnd}
          onClose={handleDrawerClose}
          ModalProps={{ keepMounted: true }}
          sx={{
            display: { xs: "block", sm: "none" },
            "& .MuiDrawer-paper": { boxSizing: "border-box", width: drawerWidth }
          }}
        >
          {drawer}
        </Drawer>
        {/* 大屏用持久化Drawer,受isDrawerOpen状态控制 */}
        <Drawer
          variant="persistent"
          open={isDrawerOpen}
          sx={{
            display: { xs: "none", sm: "block" },
            "& .MuiDrawer-paper": { boxSizing: "border-box", width: drawerWidth }
          }}
        >
          {drawer}
        </Drawer>
      </Box>
      <Box
        component="main"
        sx={{
          flexGrow: 1,
          p: 3,
          // 内容区域宽度随侧边栏状态变化
          width: isDrawerOpen ? { sm: `calc(100% - ${drawerWidth}px)` } : "100%"
        }}
      >
        <Toolbar />
        <Typography>aaa</Typography>
        <Typography>bbb</Typography>
      </Box>
    </Box>
  );
}

关键修改点说明

  • 统一状态控制:把原来仅控制小屏的状态换成了isDrawerOpen,用来统一控制所有屏幕尺寸下侧边栏的显示/隐藏状态,汉堡按钮的点击事件直接切换这个状态。
  • 大屏Drawer改为持久化模式:把大屏的variant="permanent"换成variant="persistent",这样它可以被收起/展开,并且通过open={isDrawerOpen}绑定状态,不再固定显示。
  • 自适应样式调整:给AppBar和主内容区域都添加了根据isDrawerOpen状态变化的宽度样式,确保侧边栏收起时,顶部导航和内容能占满整个屏幕宽度,展开时自动留出侧边栏的空间。
  • 保持小屏原有体验:小屏的临时Drawer逻辑不变,依然保留了过渡动画和移动端优化的keepMounted属性。

这样修改之后,不管是手机端还是大屏设备,汉堡按钮都能完美控制侧边栏的显示和隐藏,行为完全一致啦!

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.08 11:14:40