Material UI 容器查询实战:theme.containerQueries 与 sx “@” 简写语法
【免费下载链接】material-uiMaterial UI: Comprehensive React component library that implements Google's Material Design. Free forever.项目地址: https://gitcode.com/GitHub_Trending/ma/material-ui
本文基于 Material UI 官方文档 container-queries.md,系统讲解theme.containerQueries的完整 API、sx属性中@<size>/@<size>/<name>简写语法的规则与陷阱,并结合 packages/mui-system 源码揭示其底层实现机制。读完本文,你可以直接在组件库中写出“根据父容器宽度而非视口宽度”进行响应的样式,并理解 Media Query 方法如何被复用到 Container Query 场景。
为什么需要容器查询
传统的theme.breakpoints生成的@media规则判断的是浏览器视口宽度;而 CSS Container Query 判断的是元素所在容器的宽度。这使得同一组件放在侧边栏、主内容区或弹层中时能各自独立地调整布局——这正是 BasicContainerQueries.tsx 演示的“卡片在 350px / 500px 容器断点处切换横排/竖排”的场景。
Material UI 的用法约定很直接:使用theme.containerQueries加上theme.breakpoints的任意方法即可。传入的取值支持三种形式:
- 无单位数字:按像素渲染(
350等价于350px); - 字符串:如
'500px'、'40rem'; - 断点键:如
'sm'、'md'(复用主题断点的像素值)。
theme.containerQueries.up('sm'); // => '@container (min-width: 600px)'前置条件:必须有一个祖先元素声明了容器类型(如
containerType: 'inline-size'),否则容器查询不生效。官方演示中是在外层Box上通过sx写入containerType: 'inline-size'来启用该能力。
完整的 API:与 breakpoints 一一对应
容器查询支持断点 API 的全部方法,且断点取值直接继承主题的breakpoints.values(默认为xs: 0, sm: 600, md: 900, lg: 1200, xl: 1536,单位px):
| 调用 | 输出(默认断点) |
|---|---|
theme.containerQueries.up('sm') | @container (min-width: 600px) |
theme.containerQueries.down('md') | @container (max-width: 899.95px) |
theme.containerQueries.only('md') | @container (min-width: 600px) and (max-width: 1199.95px) |
theme.containerQueries.between('sm', 'lg') | @container (min-width: 600px) and (max-width: 1199.95px) |
theme.containerQueries.not('sm') | @container (max-width: 600px)(见下文范围语法说明) |
这里有一个值得注意的细节:down/between/not生成的max-width会减去step / 100(默认step: 5,即减0.05)。这个机制源自 createBreakpoints.ts 中down的实现(value) - step / 100,目的是让相邻断点互斥(up('sm')与down('sm')不重叠)。单元测试 cssContainerQueries.test.ts 明确断言了down('sm')等于@container (max-width:599.95px),所以文档示例中书写的900px只是约数,实际渲染值是899.95px。
命名容器(Named containment contexts)
若 DOM 中存在命名容器,可以在调用containerQueries时传入容器名称,访问同一套断点方法:
theme.containerQueries('sidebar').up('500px'); // => '@container sidebar (min-width: 500px)'从源码结构看,实现位于 cssContainerQueries.ts:containerQueries本身既是对象(直接调用up/down/...),又是一个函数——传入name后返回一个“附着”了同名方法的节点。核心的toContainerQuery只做字符串替换:把断点方法生成的@media ...替换为@container ...或@container <name> ...,因此它能天然继承 breakpoints 的全部取值逻辑,包括断点键解析、step偏移等。
实战示例一:styled 方式配合主题断点
以下代码完整来自 BasicContainerQueries.tsx,展示了一张房产卡片随容器宽度变化的布局:容器小于 350px 时图片在上、内容在下(flexDirection: column);达到 350px 时切换为横排;达到 500px 时图片宽度锁定为 240px、内容区内边距加大。
import { styled } from '@mui/material/styles'; import Box from '@mui/material/Box'; import Card from '@mui/material/Card'; import CardContent from '@mui/material/CardContent'; const DynamicCard = styled(Card)(({ theme }) => ({ display: 'flex', flexDirection: 'column', [theme.containerQueries.up(350)]: { flexDirection: 'row', }, })); const Image = styled('img')(({ theme }) => ({ alignSelf: 'stretch', aspectRatio: '16 / 9', objectFit: 'cover', width: '100%', maxHeight: 160, transition: '0.4s', [theme.containerQueries.up(350)]: { maxWidth: '36%', maxHeight: 'initial', }, [theme.containerQueries.up(500)]: { maxWidth: 240, }, })); const Content = styled(CardContent)(({ theme }) => ({ display: 'flex', flexDirection: 'column', gap: theme.spacing(1), padding: theme.spacing(2), flex: 'auto', transition: 'padding 0.4s', [theme.containerQueries.up(500)]: { padding: theme.spacing(3), }, })); export default function BasicContainerQueries() { return ( <Box sx={{ overflow: 'auto', resize: 'horizontal', // 允许用户拖动改变容器宽度 width: 400, maxWidth: 'min(80vw, 600px)', containerType: 'inline-size', // required for container queries }} > <DynamicCard variant="outlined"> <Image alt="The house from the offer." src="..." /> <Content>{/* ... 卡片内容 ... */}</Content> </DynamicCard> </Box> ); }演示页面配套的 ResizableDemo.js 会在 0px、350px、500px 三个位置画出虚线参考线,方便读者拖动容器时直观看到两个断点的触发位置。
实战示例二:sx 属性的@简写语法
在sx中使用时,不必显式引用主题:直接在样式值对象里用@<size>或@<size>/<name>作为键即可生成容器查询,无需引用主题。
<size>:一个宽度值(数字或带单位字符串)或断点键;<name>(可选):命名容器上下文。
以下代码取自 SxPropContainerQueries.tsx,与上一节的 styled 版本等价,但完全内联在sx中:
<Card variant="outlined" sx={{ display: 'flex', flexDirection: { '@': 'column', // 容器任意宽度时(等价 @container 0px 起点) '@350': 'row', // 容器 >= 350px 时 }, }} > <Box component="img" sx={{ maxHeight: { '@': 160, '@350': 'initial' }, maxWidth: { '@350': '36%', '@500': 240 }, }} /> <CardContent sx={{ padding: { '@': 2, '@500': 3 }, // 简写值 2/3 走 spacing 换算 }} /> </Card>简写语法的三个关键规则(Caveats)
- 无单位数值按
px渲染:@500等价于500px;但@500px是错误写法,不会被正确渲染——@前缀场景下不要再带px单位。 - 裸
@渲染为0px:'@': value表示“容器查询始终命中”的基准样式(min-width: 0px)。 - 同一组容器查询必须使用相同单位,且排序按数值大小而非单位换算:
// ✅ 单位一致,能按 0 < 20 < 40 正确升序排列 padding: { '@40em': 4, '@20em': 2, '@': 0, } // ❌ 40em 与 50px 单位不一致,排序会出错 // (40em 通常大于 50px,但按数值 40 < 50 排序) padding: { '@40em': 4, '@50': 2, '@': 0, }第 3 条规则直接对应源码:cssContainerQueries.ts 中的sortContainerQueries用正则min-width:\s*([0-9.]+)提取min-width的数值部分做纯数字排序,源码注释也明确警告 “this function does not work and will not support multiple units”(不支持混合单位)。该函数由 styleFunctionSx.js 引入,在sx处理流程中对以@container开头的键重新排序,保证输出的 CSS 按宽度从低到高排列、后写覆盖前写。
源码实现解析
1. 主题的注入点。在 createTheme.js 中,createTheme流程末尾执行muiTheme = cssContainerQueries(muiTheme),因此每个通过createTheme创建的主题都自动获得containerQueries,无需手动配置;其类型声明则放在createTheme.d.ts中合并进主题对象。
2. 断点方法的容器化包装。cssContainerQueries函数为up/down/between/only/not五个方法各生成一个包装版本,统一调用toContainerQuery:
const toContainerQuery = (mediaQuery: string, name?: string) => mediaQuery.replace('@media', name ? `@container ${name}` : '@container');3.not()的特殊处理。@media not all and (...)的写法在@container中不合法,源码对not单独做了逻辑反转:检测到not all and时,把min-width:替换为width<、max-width:替换为width>、and替换为or,生成 CSS 容器查询范围语法。因此文档中not('sm')对中间断点的语义是“容器宽度小于 600px 或大于 900px”。
4.@简写的识别与解析。sx 的响应式键处理(breakpoints.ts)中,先经isCqShorthand判断键是否为容器查询简写('@'、@<断点键>或匹配/^@\d/的数字开头),再交给getContainerQuery用正则/^@([^/]+)?\/?(.+)?$/拆出<size>与<name>,最终调用theme.containerQueries(name).up(value)。注意两点:
- 简写语法固定走
up(),即@350生成的是min-width查询; - 无效简写在开发环境(非 production)会抛出带格式提示的错误,说明合法格式为
@<breakpoint | number>或@<breakpoint | number>/<container>,例如@sm、@600、@40rem/sidebar。
5. 测试佐证。cssContainerQueries.test.ts 覆盖了:各方法在默认断点下的精确输出(如only('sm')→@container (min-width:600px) and (max-width:899.95px))、命名容器(containerQueries('sidebar').up('sm')→@container sidebar (min-width:600px)),以及isCqShorthand对@、@xs、@200、@15.5rem判真,对@media (min-width:600px)、@page判假的边界用例,可视为该功能的验收基准。
小结
Material UI 的容器查询方案可概括为三点:
theme.containerQueries复用断点 API 的全部方法(up/down/between/only/not),取值可为断点键、无单位数字或带单位字符串,支持命名容器上下文;sx中可用@<size>/@<size>/<name>简写免主题引用,但注意“无单位即 px”“裸@即 0px”“同一组查询单位必须一致”三条规则;- 使用前提是 DOM 中存在声明了
containerType(如inline-size)的祖先元素,且容器查询本身依赖浏览器的 CSS 容器查询支持。
若需要回顾断点方法本身的取值语义,可继续阅读 breakpoints 文档 及其实现 createBreakpoints.ts。
【免费下载链接】material-uiMaterial UI: Comprehensive React component library that implements Google's Material Design. Free forever.项目地址: https://gitcode.com/GitHub_Trending/ma/material-ui
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考