Yeti 框架 Container 布局:把容器查询的测量基准从视口收回到盒子自身
【免费下载链接】yetiA CSS-first, native, zero-build layout and styling framework for web designers.项目地址: https://gitcode.com/gh_mirrors/fo/yeti
本篇指南讲解 Yeti 中container布局类的完整用法与底层原理:它只做一件事——把自身的盒子变成容器查询(Container Query)的测量对象,让内部的子元素可以响应「这个盒子的宽度」而非「浏览器视口的宽度」。读完本文你将掌握:何时该用container包裹内容、如何在自有样式表中给容器命名并编写@container查询、以及它与其他会自我查询的 Yeti 布局(grid、breakout、timeline)之间的边界划分。所有结论均可在 src/layouts/container/ 的源码、manifest.json 与 test/browser/layouts/container.spec.js 中验证。
Container 是什么
container是 Yeti 的十七种布局之一(完整清单见 布局指南),它的作用是:
让自身的盒子成为容器查询的测量对象,使盒子内部的内容能够响应盒子的宽度,而不是视口的宽度。
它与stack、cluster等会排列子元素的布局不同:container本身不排列任何东西。它存在的唯一意义,是给外部的查询条件提供一个可测量的宽度边界。这一点在其源码注释中写得非常明确(container.css):
/* Container: the box a container query measures. It arranges nothing; it exists so that whatever is inside can respond to its width rather than the viewport's. Name it in your own CSS when a query must target it. */ @layer yeti.layouts { .container { container-type: inline-size; } }快速上手示例
在 HTML 中,只需把任何内容包进带container类的盒子即可:
<div class="container"> <nav class="cluster" aria-label="Site"> <a href="#">Home</a> <a href="#">Docs</a> </nav> </div>这个示例与 example.html 中展示的用法一致:外层是container,内层是一个cluster布局的导航。此时,container的内部cluster可以依据「这个容器的宽度」而不是「窗口宽度」改变自身形态(例如窄时从横排折成竖排,见下文命名容器一节)。
何时使用它
文档给出的判断标准非常精确:
围绕任何应当「按自身宽度改变形态」、且自身又无法测量自己盒子的东西来使用。
为什么需要额外一层盒子?关键限制在于:布局可以查询自身并改变子元素,但没有任何东西能从自身查询的内部改变自己的轨道。例如grid可以依据自身宽度决定列数,但grid自身无法在查询中重排自己——因为查询条件依赖的正是它自己的盒子。解决方式就是:
- 把该元素放进一个
container; - 查询这个
container的宽度; - 在查询内部改变该元素的子元素或形态。
用 CSS 术语说,这相当于「测量的盒子」与「被重新布局的盒子」被分离:container负责提供测量基准,真正改变形态的是它内部的元素。
工作原理:只有一行声明
从 container.css 可以看到,整个实现只有一行核心声明:
.container { container-type: inline-size; }container-type: inline-size声明该盒子是一个「尺寸容器」(size container),使内部元素能够使用cqw容器单位,并成为@container查询的评估基准;- 除此之外再无任何规则——不设置内边距、不约束宽度、不参与排列。
它的声明被放置在级联层yeti.layouts中(层顺序见 layers.css:yeti.reset, yeti.base, yeti.layouts, yeti.components, yeti.utilities)。这也意味着,用户自己的非分层 CSS 默认会覆盖它,这与 Yeti 整体「以用户 CSS 为准」的设计一致。
源码中的同类用法
container-type: inline-size与@container的组合在 Yeti 内部被广泛使用,可以佐证这一机制的通用性:
- grid.css:
grid在自身声明container-type: inline-size,然后通过一串@container (inline-size >= 32rem)等条件逐步增加列数,实现data-columns与data-fold的自适应; - breakout.css 与 timeline.css:同样自我声明容器类型并使用
@container; - card.css:组件内部使用
container-type: inline-size并在@container (inline-size < 22rem)下调整布局。
注意其中的细节:查询阈值直接写成数字(如22rem),因为容器条件无法读取 CSS 变量(token)——这是 Yeti 设计上的一个硬约束,在 layouts.md 中被明确说明。
按名称定位容器:container-name与@container
container类本身不暴露任何属性(见下文),但如果你希望在自有 CSS 中精确地定位某个容器,需要给它命名。文档特别强调:命名只能来自样式表,不能来自 HTML 属性,即不存在data-name之类的写法。正确做法是在你的样式表中追加一条规则:
.container.sidebar-slot { container-name: slot; } @container slot (inline-size < 30rem) { .cluster { flex-direction: column; } }这里:
.container.sidebar-slot通过额外类名sidebar-slot把该容器命名为slot(container-name属性);- 随后
@container slot (inline-size < 30rem)表示:当名为slot的容器内联尺寸小于30rem时,将其中的.cluster从横排切换为竖排。
与「自我查询」布局的分工
文档明确划出了一条边界,避免误用:
Yeti 中会自我查询的布局(使用
data-fold的grid、带data-note的breakout、timeline)不需要container包裹——它们已经把自己的盒子当作测量对象。
因此container的真正适用对象是两类场景:
- 你自己的自定义查询:需要在自有 CSS 中编写
@container规则; - 必须重排自身的组件:一个组件需要根据自身所处的盒子宽度改变自己的布局,而它无法通过内部查询改变自己的轨道时,就用
container包一层再查询。
配置项:属性、子元素与 Token
按照 manifest.json 的声明,container是 Yeti 中配置面最小的一类布局:
| 配置项 | 内容 |
|---|---|
| 属性(attributes) | 无。完全通过子元素与 token 配置 |
| 类变体(classes) | 无 |
| 子元素(children) | > *,至少 1 个,任意元素。容器自身不排列任何内容 |
| Token | 无公开 token |
这与其他需要data-gap、data-width等属性配置的布局(如stack、sidebar)形成鲜明对比——container的整个公共 API 就是那个类名本身。
无障碍
manifest.json 的a11y字段指出:该组件纯结构性的(purely structural),不要求任何 ARIA 属性,也不涉及键盘交互。测试 container.spec.js 中通过 axe 检查确认其没有任何无障碍违规项。
浏览器支持
| 场景 | 支持方式 |
|---|---|
| 无守卫直接使用 | 容器尺寸查询(container size queries),即container-type: inline-size与@container |
@supports守卫下 | 无——没有任何需要额外降级处理的规则 |
也就是说,该组件依赖现代浏览器原生的容器查询能力,不附带任何 polyfill 或回退逻辑。
JavaScript:不需要
container是纯 CSS 组件,不包含任何 JavaScript(manifest 中js字段为null)。引入方式与 Yeti 其余部分一致:通过src/yeti.css或按需引入对应样式文件即可。
用测试验证行为
test/browser/layouts/container.spec.js 中的用例精确验证了本文所述的核心语义,可作为理解与复用的参照:
- 查询按容器宽度触发,而非视口:固定视口宽度为
1000时,#a与#b(位于container内的cluster子项)处于同一行;将舞台缩到400后,#b落到#a之下——查询因容器变窄而触发。 - 仅改变视口不会触发查询:容器宽度不变,仅把视口从
1000改为500,两个子项依然保持同行——证明测量基准是容器盒子而非窗口。 - 无无障碍违规:axe 扫描返回零违规。
对应的测试夹具在 test/browser/fixtures/layouts/container.html:一个#probe的.container内嵌.cluster,并在样式表中用@container (inline-size < 30rem)切换.cluster的方向——与文档示例中的写法一致。
关于命名的一点历史
文档「Why this name」一节说明:container的含义是「它包含内容」,并且它正是容器查询要测量的东西。命名上与 Foundation 6 有直接对照:Foundation 6 的.grid-container承担的是「页面栏宽」职责,而那个职责在 Yeti 中由center承担(center将一列内容水平居中并约束最大宽度)。换言之,不要在 Yeti 中把container当页面列宽容器用——那是center的工作;container只负责提供可查询的盒子。
小结
container的完整实现只有一行:container-type: inline-size,且不参与任何排列;- 用它的时机:内容需要「按自身盒子宽度改变形态」且无法自我查询时,包一层再查询;
- 命名只能写在样式表里(
container-name),配合@container <name> (inline-size < 30rem)使用; - 无属性、无 token、无 JavaScript、纯结构性、无无障碍负担;
- 依赖原生容器尺寸查询,无
@supports降级; - 自 Yeti 7.0.0 起可用;内部布局
grid/breakout/timeline已自我查询,无需container包裹。
【免费下载链接】yetiA CSS-first, native, zero-build layout and styling framework for web designers.项目地址: https://gitcode.com/gh_mirrors/fo/yeti
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考