如何在React中设置Material Drawer宽度与子元素一致
解决方案:MUI永久抽屉自适应子元素宽度(仅显示图标)
问题分析
原示例依赖固定drawerWidth常量控制抽屉、AppBar和主内容区的布局,直接删除会导致布局错乱。要实现抽屉宽度自适应图标,需要调整Drawer的样式规则,同时修改关联的AppBar和Main组件的布局逻辑,不再依赖固定宽度值。
具体修改步骤
- 移除固定宽度常量:删除
const drawerWidth = 60;,不再用固定值约束宽度。 - 调整Drawer组件样式:修改Drawer的sx属性,让抽屉容器和内部paper元素宽度自适应内容,同时保持
flexShrink:0防止被压缩:
<Drawer sx={{ flexShrink: 0, "& .MuiDrawer-paper": { width: "auto", boxSizing: "border-box", padding: theme.spacing(0, 1), // 可选:给图标加内边距优化视觉 }, }} variant="persistent" anchor="left" open={open} >
- 修改AppBar的自适应布局:将原来依赖
drawerWidth的固定宽度计算改为动态适配,使用marginLeft配合抽屉的实际宽度,同时调整过渡效果:
const AppBar = styled(MuiAppBar, { shouldForwardProp: (prop) => prop !== "open", })<AppBarProps>(({ theme, open }) => ({ transition: theme.transitions.create(["margin"], { easing: theme.transitions.easing.sharp, duration: theme.transitions.duration.leavingScreen, }), ...(open && { marginLeft: "auto", // 改为自动适配抽屉宽度 transition: theme.transitions.create(["margin"], { easing: theme.transitions.easing.easeOut, duration: theme.transitions.duration.enteringScreen, }), }), }));
- 调整Main组件的布局:移除依赖固定宽度的
marginLeft: -${drawerWidth}px,改用transform实现抽屉展开/收起的过渡效果,避免固定宽度限制:
const Main = styled("main", { shouldForwardProp: (prop) => prop !== "open" })<{ open?: boolean; }>(({ theme, open }) => ({ flexGrow: 1, padding: theme.spacing(3), transition: theme.transitions.create("transform", { easing: theme.transitions.easing.sharp, duration: theme.transitions.duration.leavingScreen, }), transform: open ? "translateX(0)" : "translateX(-100%)", }));
- 确保仅显示图标:确认ListItem中已移除
ListItemText组件,只保留ListItemIcon,让抽屉内容仅为图标,宽度自然适配。
修改后的完整代码
import * as React from "react"; import { styled, useTheme } from "@mui/material/styles"; import Box from "@mui/material/Box"; import Drawer from "@mui/material/Drawer"; import CssBaseline from "@mui/material/CssBaseline"; import MuiAppBar, { AppBarProps as MuiAppBarProps } from "@mui/material/AppBar"; import Toolbar from "@mui/material/Toolbar"; import List from "@mui/material/List"; import Typography from "@mui/material/Typography"; import Divider from "@mui/material/Divider"; import IconButton from "@mui/material/IconButton"; import MenuIcon from "@mui/icons-material/Menu"; import ChevronLeftIcon from "@mui/icons-material/ChevronLeft"; import ChevronRightIcon from "@mui/icons-material/ChevronRight"; import ListItem from "@mui/material/ListItem"; import ListItemButton from "@mui/material/ListItemButton"; import ListItemIcon from "@mui/material/ListItemIcon"; import InboxIcon from "@mui/icons-material/MoveToInbox"; import MailIcon from "@mui/icons-material/Mail"; const Main = styled("main", { shouldForwardProp: (prop) => prop !== "open" })<{ open?: boolean; }>(({ theme, open }) => ({ flexGrow: 1, padding: theme.spacing(3), transition: theme.transitions.create("transform", { easing: theme.transitions.easing.sharp, duration: theme.transitions.duration.leavingScreen, }), transform: open ? "translateX(0)" : "translateX(-100%)", })); interface AppBarProps extends MuiAppBarProps { open?: boolean; } const AppBar = styled(MuiAppBar, { shouldForwardProp: (prop) => prop !== "open", })<AppBarProps>(({ theme, open }) => ({ transition: theme.transitions.create(["margin"], { easing: theme.transitions.easing.sharp, duration: theme.transitions.duration.leavingScreen, }), ...(open && { marginLeft: "auto", transition: theme.transitions.create(["margin"], { easing: theme.transitions.easing.easeOut, duration: theme.transitions.duration.enteringScreen, }), }), })); const DrawerHeader = styled("div")(({ theme }) => ({ display: "flex", alignItems: "center", padding: theme.spacing(0, 1), ...theme.mixins.toolbar, justifyContent: "flex-end", })); export default function PersistentDrawerLeft() { const theme = useTheme(); const [open, setOpen] = React.useState(false); const handleDrawerOpen = () => { setOpen(true); }; const handleDrawerClose = () => { setOpen(false); }; return ( <Box sx={{ display: "flex" }}> <CssBaseline /> <AppBar position="fixed" open={open}> <Toolbar> <IconButton color="inherit" aria-label="open drawer" onClick={handleDrawerOpen} edge="start" sx={{ mr: 2, ...(open && { display: "none" }) }} > <MenuIcon /> </IconButton> <Typography variant="h6" noWrap component="div"> Persistent drawer </Typography> </Toolbar> </AppBar> <Drawer sx={{ flexShrink: 0, "& .MuiDrawer-paper": { width: "auto", boxSizing: "border-box", padding: theme.spacing(0, 1), }, }} variant="persistent" anchor="left" open={open} > <DrawerHeader> <IconButton onClick={handleDrawerClose}> {theme.direction === "ltr" ? ( <ChevronLeftIcon /> ) : ( <ChevronRightIcon /> )} </IconButton> </DrawerHeader> <Divider /> <List> {["Inbox", "Starred", "Send email", "Drafts"].map((text, index) => ( <ListItem key={text} disablePadding> <ListItemButton> <ListItemIcon> {index % 2 === 0 ? <InboxIcon /> : <MailIcon />} </ListItemIcon> </ListItemButton> </ListItem> ))} </List> <Divider /> </Drawer> <Main open={open}> <DrawerHeader /> <Typography paragraph> Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Rhoncus dolor purus non enim praesent elementum facilisis leo vel. Risus at ultrices mi tempus imperdiet. Semper risus in hendrerit gravida rutrum quisque non tellus. Convallis convallis tellus id interdum velit laoreet id donec ultrices. Odio morbi quis commodo odio aenean sed adipiscing. Amet nisl suscipit adipiscing bibendum est ultricies integer quis. Cursus euismod quis viverra nibh cras. Metus vulputate eu scelerisque felis imperdiet proin fermentum leo. Mauris commodo quis imperdiet massa tincidunt. Cras tincidunt lobortis feugiat vivamus at augue. At augue eget arcu dictum varius duis at consectetur lorem. Velit sed ullamcorper morbi tincidunt. Lorem donec massa sapien faucibus et molestie ac. </Typography> </Main> </Box> ); }
说明
- 抽屉宽度通过
width: "auto"自动适配内部图标宽度,无需固定值 - AppBar使用
marginLeft: "auto"自动适配抽屉展开后的宽度 - 主内容区改用
transform实现滑动过渡,避免依赖固定宽度的负边距 - 可选添加内边距优化图标与抽屉边缘的间距
内容的提问来源于stack exchange,提问作者user16971617
相关产品推荐
相关产品推荐

