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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.04 00:34:53