Material-UI 响应式设计 #

概述 #

MUI 提供了完整的响应式设计系统,包括断点、Grid 布局、隐藏组件等。

text
响应式系统
│
├── breakpoints    断点系统
├── Grid           响应式网格
├── Hidden         条件渲染
├── useMediaQuery  媒体查询
└── sx prop        响应式样式

断点系统 #

默认断点 #

jsx
const defaultBreakpoints = {
  xs: 0,      // 手机
  sm: 600,    // 平板竖屏
  md: 900,    // 平板横屏
  lg: 1200,   // 桌面
  xl: 1536,   // 大屏
};

自定义断点 #

jsx
import { createTheme } from '@mui/material/styles';

const theme = createTheme({
  breakpoints: {
    values: {
      mobile: 0,
      tablet: 640,
      laptop: 1024,
      desktop: 1280,
    },
  },
});

断点范围 #

text
xs: 0px - 599px
sm: 600px - 899px
md: 900px - 1199px
lg: 1200px - 1535px
xl: 1536px+

useMediaQuery #

基础用法 #

jsx
import { useMediaQuery, useTheme } from '@mui/material';

function ResponsiveComponent() {
  const theme = useTheme();
  const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
  const isTablet = useMediaQuery(theme.breakpoints.between('sm', 'md'));
  const isDesktop = useMediaQuery(theme.breakpoints.up('md'));

  return (
    <div>
      {isMobile && <MobileView />}
      {isTablet && <TabletView />}
      {isDesktop && <DesktopView />}
    </div>
  );
}

断点方法 #

jsx
const theme = useTheme();

theme.breakpoints.up('sm');
theme.breakpoints.down('sm');
theme.breakpoints.between('sm', 'md');
theme.breakpoints.only('sm');
theme.breakpoints.not('sm');

JavaScript 媒体查询 #

jsx
const matches = useMediaQuery('(min-width:600px)');
const isDark = useMediaQuery('(prefers-color-scheme: dark)');
const isPrint = useMediaQuery('print');

sx Prop 响应式 #

响应式属性值 #

jsx
<Box
  sx={{
    width: {
      xs: '100%',
      sm: '80%',
      md: '60%',
      lg: '50%',
    },
    p: {
      xs: 1,
      sm: 2,
      md: 3,
    },
    fontSize: {
      xs: '0.875rem',
      md: '1rem',
    },
  }}
>
  响应式 Box
</Box>

响应式样式对象 #

jsx
<Box
  sx={{
    display: {
      xs: 'block',
      md: 'flex',
    },
    flexDirection: {
      xs: 'column',
      md: 'row',
    },
    gap: {
      xs: 1,
      md: 2,
      lg: 3,
    },
  }}
>
  响应式布局
</Box>

条件样式 #

jsx
<Box
  sx={{
    bgcolor: {
      xs: 'primary.light',
      md: 'primary.main',
    },
    color: {
      xs: 'primary.main',
      md: 'primary.contrastText',
    },
  }}
>
  响应式颜色
</Box>

Grid 响应式布局 #

基础响应式网格 #

jsx
<Grid container spacing={2}>
  <Grid item xs={12} sm={6} md={4} lg={3}>
    <Box sx={{ p: 2, bgcolor: 'grey.200' }}>项目 1</Box>
  </Grid>
  <Grid item xs={12} sm={6} md={4} lg={3}>
    <Box sx={{ p: 2, bgcolor: 'grey.200' }}>项目 2</Box>
  </Grid>
  <Grid item xs={12} sm={6} md={4} lg={3}>
    <Box sx={{ p: 2, bgcolor: 'grey.200' }}>项目 3</Box>
  </Grid>
  <Grid item xs={12} sm={6} md={4} lg={3}>
    <Box sx={{ p: 2, bgcolor: 'grey.200' }}>项目 4</Box>
  </Grid>
</Grid>

响应式间距 #

jsx
<Grid container spacing={{ xs: 1, sm: 2, md: 3 }}>
  {/* ... */}
</Grid>

响应式方向 #

jsx
<Grid
  container
  direction={{ xs: 'column', md: 'row' }}
  spacing={2}
>
  <Grid item xs={12} md={6}>
    左侧
  </Grid>
  <Grid item xs={12} md={6}>
    右侧
  </Grid>
</Grid>

响应式对齐 #

jsx
<Grid
  container
  justifyContent={{ xs: 'center', md: 'flex-start' }}
  alignItems={{ xs: 'center', md: 'flex-start' }}
>
  {/* ... */}
</Grid>

Hidden 组件(已弃用) #

注意:Hidden 组件在 MUI v5 中已弃用,推荐使用 sx prop 或 useMediaQuery。

替代方案 #

jsx
// 旧方式
<Hidden smDown>
  <div>仅在 sm 及以上显示</div>
</Hidden>

// 新方式 - 使用 sx prop
<Box sx={{ display: { xs: 'none', sm: 'block' } }}>
  仅在 sm 及以上显示
</Box>

// 新方式 - 使用 useMediaQuery
function MyComponent() {
  const theme = useTheme();
  const isSmUp = useMediaQuery(theme.breakpoints.up('sm'));

  if (!isSmUp) return null;
  return <div>仅在 sm 及以上显示</div>;
}

响应式图片 #

基础响应式图片 #

jsx
<Box
  component="img"
  sx={{
    width: {
      xs: '100%',
      md: '50%',
    },
    height: 'auto',
  }}
  src="/image.jpg"
  alt="响应式图片"
/>

不同尺寸图片 #

jsx
<picture>
  <source media="(min-width: 1200px)" srcSet="/large.jpg" />
  <source media="(min-width: 600px)" srcSet="/medium.jpg" />
  <img src="/small.jpg" alt="响应式图片" style={{ width: '100%' }} />
</picture>

响应式排版 #

响应式字体大小 #

jsx
<Typography
  sx={{
    fontSize: {
      xs: '1.5rem',
      sm: '2rem',
      md: '2.5rem',
      lg: '3rem',
    },
  }}
>
  响应式标题
</Typography>

响应式字体 #

jsx
const theme = createTheme({
  typography: {
    h1: {
      fontSize: '2.5rem',
      '@media (min-width:600px)': {
        fontSize: '3rem',
      },
      '@media (min-width:900px)': {
        fontSize: '4rem',
      },
    },
  },
});

使用 responsiveFontSizes #

jsx
import { createTheme, responsiveFontSizes } from '@mui/material/styles';

let theme = createTheme();
theme = responsiveFontSizes(theme);

响应式组件 #

响应式导航 #

jsx
function ResponsiveNav() {
  const theme = useTheme();
  const isMobile = useMediaQuery(theme.breakpoints.down('md'));

  return (
    <AppBar position="static">
      <Toolbar>
        <Typography variant="h6" sx={{ flexGrow: 1 }}>
          Logo
        </Typography>
        {isMobile ? (
          <IconButton color="inherit">
            <MenuIcon />
          </IconButton>
        ) : (
          <Stack direction="row" spacing={2}>
            <Button color="inherit">首页</Button>
            <Button color="inherit">产品</Button>
            <Button color="inherit">关于</Button>
          </Stack>
        )}
      </Toolbar>
    </AppBar>
  );
}

响应式侧边栏 #

jsx
function ResponsiveSidebar() {
  const theme = useTheme();
  const isMobile = useMediaQuery(theme.breakpoints.down('md'));
  const [open, setOpen] = useState(false);

  const drawer = (
    <Box sx={{ width: 250 }}>
      <List>
        <ListItem button>
          <ListItemText primary="首页" />
        </ListItem>
        <ListItem button>
          <ListItemText primary="设置" />
        </ListItem>
      </List>
    </Box>
  );

  return (
    <Box sx={{ display: 'flex' }}>
      {isMobile ? (
        <Drawer open={open} onClose={() => setOpen(false)}>
          {drawer}
        </Drawer>
      ) : (
        <Drawer variant="permanent">{drawer}</Drawer>
      )}
      <Box component="main" sx={{ flexGrow: 1, p: 3 }}>
        {isMobile && (
          <IconButton onClick={() => setOpen(true)}>
            <MenuIcon />
          </IconButton>
        )}
        <Typography>主内容</Typography>
      </Box>
    </Box>
  );
}

响应式表格 #

jsx
function ResponsiveTable({ data }) {
  const theme = useTheme();
  const isMobile = useMediaQuery(theme.breakpoints.down('sm'));

  if (isMobile) {
    return (
      <Stack spacing={2}>
        {data.map((row) => (
          <Card key={row.id}>
            <CardContent>
              <Typography variant="h6">{row.name}</Typography>
              <Typography>价格: ¥{row.price}</Typography>
              <Typography>数量: {row.quantity}</Typography>
            </CardContent>
          </Card>
        ))}
      </Stack>
    );
  }

  return (
    <TableContainer component={Paper}>
      <Table>
        <TableHead>
          <TableRow>
            <TableCell>名称</TableCell>
            <TableCell>价格</TableCell>
            <TableCell>数量</TableCell>
          </TableRow>
        </TableHead>
        <TableBody>
          {data.map((row) => (
            <TableRow key={row.id}>
              <TableCell>{row.name}</TableCell>
              <TableCell>¥{row.price}</TableCell>
              <TableCell>{row.quantity}</TableCell>
            </TableRow>
          ))}
        </TableBody>
      </Table>
    </TableContainer>
  );
}

实战示例:响应式布局 #

jsx
function ResponsiveLayout() {
  const theme = useTheme();
  const isMobile = useMediaQuery(theme.breakpoints.down('md'));

  return (
    <Box sx={{ display: 'flex', flexDirection: 'column', minHeight: '100vh' }}>
      <AppBar position="static">
        <Toolbar>
          {isMobile && (
            <IconButton edge="start" color="inherit" sx={{ mr: 2 }}>
              <MenuIcon />
            </IconButton>
          )}
          <Typography variant="h6" sx={{ flexGrow: 1 }}>
            响应式应用
          </Typography>
          {!isMobile && (
            <Stack direction="row" spacing={1}>
              <Button color="inherit">首页</Button>
              <Button color="inherit">产品</Button>
              <Button color="inherit">关于</Button>
            </Stack>
          )}
        </Toolbar>
      </AppBar>

      <Container maxWidth="lg" sx={{ mt: 4, mb: 4, flexGrow: 1 }}>
        <Grid container spacing={3}>
          <Grid item xs={12} md={8}>
            <Paper sx={{ p: 2 }}>
              <Typography variant="h4" gutterBottom>
                主内容区
              </Typography>
              <Typography>
                这是主内容区域,在移动端占满宽度,在桌面端占 2/3 宽度。
              </Typography>
            </Paper>
          </Grid>
          <Grid item xs={12} md={4}>
            <Paper sx={{ p: 2 }}>
              <Typography variant="h6" gutterBottom>
                侧边栏
              </Typography>
              <Typography variant="body2">
                这是侧边栏,在移动端占满宽度,在桌面端占 1/3 宽度。
              </Typography>
            </Paper>
          </Grid>
        </Grid>
      </Container>

      <Box
        component="footer"
        sx={{
          py: 3,
          px: 2,
          mt: 'auto',
          bgcolor: 'background.paper',
        }}
      >
        <Container maxWidth="lg">
          <Typography variant="body2" align="center">
            © 2024 响应式应用
          </Typography>
        </Container>
      </Box>
    </Box>
  );
}

下一步 #

继续学习 图标系统,了解 Material Icons 的使用方法!

最后更新:2026-03-28