MUI响应式容器中Grid适配Drawer开合布局问题求助
实现Drawer开合时动态调整Grid列数(MUI + NextJS)
问题描述
想要实现类似YouTube的布局效果:侧边Drawer开合时,改变主容器宽度,让Grid自动调整列数。但当前基于MUI(5.15.0)+NextJS(14.0.4)+React(18.2.0)的代码无法触发该布局变化,核心原因是MUI的Grid组件默认监听窗口视口宽度,而非父容器的实际可用宽度。
解决方案
要实现基于父容器宽度的响应式Grid,需要脱离MUI预设的视口断点,自定义容器宽度监听逻辑,动态控制Grid列数。
步骤1:编写自定义容器宽度监听Hook
创建一个Hook来监听目标容器的宽度变化,包含窗口resize和容器样式变化的监听:
import { useState, useEffect, useRef } from 'react'; export const useContainerWidth = () => { const containerRef = useRef<HTMLDivElement>(null); const [width, setWidth] = useState(0); useEffect(() => { const updateWidth = () => { containerRef.current && setWidth(containerRef.current.offsetWidth); }; // 初始化宽度 updateWidth(); // 监听窗口resize window.addEventListener('resize', updateWidth); // 监听容器样式变化(Drawer开合会改变容器宽度) const observer = new MutationObserver(updateWidth); containerRef.current && observer.observe(containerRef.current, { attributes: true, attributeFilter: ['style', 'class'] }); return () => { window.removeEventListener('resize', updateWidth); observer.disconnect(); }; }, []); return { containerRef, width }; };
步骤2:修正Main容器的宽度逻辑
调整Header组件中Main的样式,确保Drawer开合时容器宽度正确变化:
const Main = styled('main', { shouldForwardProp: (prop) => prop !== 'open' })<{ open?: boolean; }>(({ theme, open }) => ({ flexGrow: 1, transition: theme.transitions.create('width', { easing: theme.transitions.easing.easeOut, duration: theme.transitions.duration.enteringScreen, }), // 根据Drawer状态计算容器宽度 width: open ? `calc(100% - ${drawerWidth}px)` : `calc(100% - ${theme.spacing(8)}px)`, overflow: 'auto' }));
步骤3:基于容器宽度动态控制Grid列数
在主体部分替换原Grid的断点属性,用自定义宽度计算列数:
import { useContainerWidth } from '../hooks/useContainerWidth'; // ... const { containerRef, width } = useContainerWidth(); // 模拟MUI断点逻辑,根据容器宽度返回列数 const getColumnCount = () => { if (width >= 1920) return 5; // 对应xxl if (width >= 1200) return 4; // 对应lg if (width >= 900) return 3; // 对应md if (width >= 600) return 2; // 对应sm return 1; // 对应xs }; return ( <Box ref={containerRef} sx={{ mt: 2, mb: 2, ml: -1, mr: -1 }}> <Unstable_Grid2 container spacing={2}> {events?.map((event: EventSummary) => ( <Unstable_Grid2 key={event.id + event.starts_at} sx={{ flex: `0 0 ${100 / getColumnCount()}%`, maxWidth: `${100 / getColumnCount()}%`, transition: 'flex 0.3s ease, max-width 0.3s ease' // 添加平滑过渡 }} > <EventCard event={event} /> </Unstable_Grid2> ))} </Unstable_Grid2 > </Box> );
可选优化:直接监听Drawer状态
如果MutationObserver未捕获到Drawer开合的宽度变化,可以将Header中的open状态通过props/Context传递到主体组件,强制触发宽度更新:
// 在Layout组件中传递Drawer状态 export default function Layout(props: { children: React.ReactNode, body: React.ReactNode, filters: React.ReactNode, }) { const [drawerOpen, setDrawerOpen] = useState(false); return ( <ServerAuthProvider> <Header maxWidth={false} onDrawerToggle={(open) => setDrawerOpen(open)} > <Container maxWidth={false}> <Box ml={2} mt={10}> {props.filters} {React.cloneElement(props.body as React.ReactElement, { drawerOpen })} {props.children} </Box> </Container> </Header> </ServerAuthProvider> ); } // 在主体组件中监听状态变化 const { drawerOpen } = props; useEffect(() => { containerRef.current && setWidth(containerRef.current.offsetWidth); }, [drawerOpen]);
核心逻辑说明
- 放弃MUI基于视口的断点,改用容器实际宽度计算列数
- 通过
MutationObserver监听容器样式变化,确保Drawer开合时能触发宽度更新 - 用
flex和maxWidth属性模拟Grid的响应式列布局,添加过渡动画提升体验
内容的提问来源于stack exchange,提问作者Leonardo
相关产品推荐
相关产品推荐

