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

React Material UI Mini Variant与Responsive Drawer适配移动端开发咨询

实现方案

核心思路是通过MUI内置的响应式hook监听屏幕尺寸,动态切换抽屉的交互模式:

第一步:新增依赖导入

在代码顶部导入需要的hooks:

import { useTheme } from '@material-ui/core/styles';
import useMediaQuery from '@material-ui/core/useMediaQuery';

第二步:组件内新增响应式判断逻辑

在SideMenuLayout组件内const classes = useStyles();后面添加以下代码:

const theme = useTheme();
// 以sm断点(默认600px)作为移动端分界,可按需调整为xs/md等
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));

// 可选优化:屏幕切换为移动端时自动关闭抽屉
useEffect(() => {
  if (isMobile) setDrawerOpen(false);
}, [isMobile, setDrawerOpen]);

第三步:调整组件属性适配响应式逻辑

修改AppBar样式逻辑

将AppBar的className修改为仅非移动端时才触发偏移:

<AppBar 
  position="absolute" 
  className={clsx(classes.appBar, !isMobile && drawerOpen && classes.appBarShift)}
>

修改菜单按钮显示逻辑

将菜单按钮的className修改为仅非移动端展开时才隐藏:

<IconButton
  edge="start"
  color="inherit"
  aria-label="open drawer"
  onClick={handleDrawerOpen}
  className={clsx(classes.menuButton, !isMobile && drawerOpen && classes.menuButtonHidden)}
>
  <MenuIcon />
</IconButton>

修改Drawer组件配置

动态切换抽屉类型,移动端使用临时抽屉模式:

<Drawer
  variant={isMobile ? 'temporary' : 'permanent'}
  onClose={isMobile ? handleDrawerClose : undefined}
  classes={{
    paper: clsx(classes.drawerPaper, !isMobile && !drawerOpen && classes.drawerPaperClose),
  }}
  open={drawerOpen}
>

完整修改后代码

import React, {useState, useEffect} from 'react';
import clsx from 'clsx';
import { makeStyles, useTheme } from '@material-ui/core/styles';
import useMediaQuery from '@material-ui/core/useMediaQuery';
import CssBaseline from '@material-ui/core/CssBaseline';
import Drawer from '@material-ui/core/Drawer';
import AppBar from '@material-ui/core/AppBar';
import Toolbar from '@material-ui/core/Toolbar';
import List from '@material-ui/core/List';
import Typography from '@material-ui/core/Typography';
import Divider from '@material-ui/core/Divider';
import IconButton from '@material-ui/core/IconButton';
import MenuIcon from '@material-ui/icons/Menu';
import MenuOpenIcon from '@material-ui/icons/MenuOpen';

import { MainListItems } from './listItems';
import AccountMenu from '../../components/AccountMenu'
import ChangePasswordDialog from './ChangePasswordDialog';

const drawerWidth = 240;

const useStyles = makeStyles((theme) => ({
  root: {
    display: 'flex',
  },
  toolbar: {
    paddingRight: 60, 
  },
  toolbarIcon: {
    display: 'flex',
    alignItems: 'center',
    justifyContent: 'flex-end',
    padding: '0 8px',
    ...theme.mixins.toolbar,
  },
  appBar: {
    zIndex: theme.zIndex.drawer + 1,
    transition: theme.transitions.create(['width', 'margin'], {
      easing: theme.transitions.easing.sharp,
      duration: theme.transitions.duration.leavingScreen,
    }),
  },
  appBarShift: {
    marginLeft: drawerWidth,
    width: `calc(100% - ${drawerWidth}px)`,
    transition: theme.transitions.create(['width', 'margin'], {
      easing: theme.transitions.easing.sharp,
      duration: theme.transitions.duration.enteringScreen,
    }),
  },
  menuButton: {
    marginRight: 36,
  },
  menuButtonHidden: {
    display: 'none',
  },
  mainTitle: {
    flexGrow: 1,
    marginLeft: 12
  },
  title: {
    flexGrow: 1,
  },
  drawerPaper: {
    position: 'relative',
    whiteSpace: 'nowrap',
    width: drawerWidth,
    transition: theme.transitions.create('width', {
      easing: theme.transitions.easing.sharp,
      duration: theme.transitions.duration.enteringScreen,
    }),
    // 适配移动端临时抽屉的层级和定位
    [theme.breakpoints.down('sm')]: {
      position: 'fixed',
    }
  },
  drawerPaperClose: {
    overflowX: 'hidden',
    transition: theme.transitions.create('width', {
      easing: theme.transitions.easing.sharp,
      duration: theme.transitions.duration.leavingScreen,
    }),
    width: theme.spacing.unit * 7,
    [theme.breakpoints.up('sm')]: {
      width: theme.spacing.unit * 9,
    },
  },
  appBarSpacer: theme.mixins.toolbar,
  content: {
    flexGrow: 1,
    height: '100vh',
    overflow: 'auto',
  },
  container: {
    paddingTop: theme.spacing(4),
    paddingBottom: theme.spacing(4),
  },
  paper: {
    padding: theme.spacing(2),
    display: 'flex',
    overflow: 'auto',
    flexDirection: 'column',
  },
  fixedHeight: {
    height: 240,
  },
}));

export default function SideMenuLayout({
  drawerOpen,
  setDrawerOpen,
  children,
  title
}) {
  const classes = useStyles();
  const theme = useTheme();
  const isMobile = useMediaQuery(theme.breakpoints.down('sm'));

  useEffect(() => {
    if (isMobile) setDrawerOpen(false);
  }, [isMobile, setDrawerOpen]);

  const handleDrawerOpen = () => {
    setDrawerOpen(true);
  };
  const handleDrawerClose = () => {
    setDrawerOpen(false);
  };
  
  const [changePasswordDialogOpen, setChangePasswordDialogOpen] = useState(false)

  return (
    <div className={classes.root}>
      <CssBaseline />
      <AppBar position="absolute" className={clsx(classes.appBar, !isMobile && drawerOpen && classes.appBarShift)}>
        <Toolbar className={classes.toolbar}>
          <IconButton
            edge="start"
            color="inherit"
            aria-label="open drawer"
            onClick={handleDrawerOpen}
            className={clsx(classes.menuButton, !isMobile && drawerOpen && classes.menuButtonHidden)}
          >
            <MenuIcon />
          </IconButton>
          <Typography component="h1" variant="h6" color="inherit" noWrap className={classes.title}>
            {drawerOpen ? title : `MyTitle - ${title}`}
          </Typography>
          <AccountMenu onChangePassword={() => setChangePasswordDialogOpen(true)} />          
        </Toolbar>
      </AppBar>
      <Drawer
        variant={isMobile ? 'temporary' : 'permanent'}
        onClose={isMobile ? handleDrawerClose : undefined}
        classes={{
          paper: clsx(classes.drawerPaper, !isMobile && !drawerOpen && classes.drawerPaperClose),
        }}
        open={drawerOpen}
      >
        <div className={classes.toolbarIcon}>
          <Typography component="h1" variant="h6" color="inherit" noWrap className={classes.mainTitle}>
            MyTitle
          </Typography>
          <IconButton onClick={handleDrawerClose}>
            <MenuOpenIcon />
          </IconButton>
        </div>
        <Divider />
        <List><MainListItems /></List>
      </Drawer>
      <main className={classes.content}>
        <div className={classes.appBarSpacer} />
        {children}
      </main>
      
      <ChangePasswordDialog
        isOpen={changePasswordDialogOpen}
        setOpen={setChangePasswordDialogOpen}
      />
    </div>
  );
}

关键逻辑说明

  • 移动端抽屉为临时悬浮模式,点击外部区域自动关闭,不会挤压主内容区域
  • 桌面端完全保留原有的Mini Variant抽屉的收缩/展开交互逻辑
  • 可通过调整theme.breakpoints.down('sm')的断点值,自定义响应式切换的触发宽度

内容的提问来源于stack exchange,提问作者Son Blake

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.05 15:15:01