克己笔记 克己笔记
技术笔记

容器查询实战:一个博客主题里的七处改写

18

博客主题里最容易腐坏的东西是媒体查询。我数了一下,改到第 1.3 代的时候,base.css 里已经躺着 二十七个 @media,断点从 480 一直排到 1440,彼此之间还互相覆写。每次想调一个卡片的间距,都得先确认「在 921 到 1379 之间它长什么样」。

容器查询(@container)解决的就是这件事。

一、媒体查询答错了问题

媒体查询问的是:视口有多宽?

但组件真正关心的是:我被放进了一个多宽的框里?

这两件事在单栏页面里基本等价,一旦布局复杂起来就彻底分家了。同一个卡片组件,放在主内容列里是 680px 宽,放进侧栏就剩 260px 宽——而视口宽度从头到尾没变过。媒体查询对此一无所知,你只能给它硬编码一个「在侧栏里」的上下文,于是就有了 .sidebar .card { ... } 这种选择器,以及它带来的一整套覆写链。

二、最小可用写法

容器查询要用两半:容器声明查询

先在父元素上声明这是一个查询容器,并告诉浏览器你要按哪个方向量:

.card-host {
  container-type: inline-size;
  container-name: card;
}

inline-size 表示只关心宽度,这也是唯一被广泛支持的类型。声明之后,这个元素自己的尺寸不会被子元素撑开——它是从外部拿宽度的,这一点后面会说。

然后在子元素里按容器宽度写条件:

.card { padding: 20px; }

@container card (min-width: 420px) {
  .card {
    display: grid;
    grid-template-columns: 120px 1fr;
    gap: 20px;
  }
}

读作:只要我被塞进一个宽度 ≥ 420px 的卡片宿主里,就切换成两栏。

关键是这里没有任何一处提到屏幕。同一个 .card,放进 680px 的主列会走两栏,放进 260px 的侧栏会走单栏,一行视口相关的代码都不需要。

三、我在主题里改掉的七处

第一处:文章卡片。 原来是「≥1100 视口时图片在左,文字在右」,改成按卡片自身宽度判断。侧栏里的紧凑列表复用同一个组件后就自动变成上下结构,删掉了 34 行覆写。

第二处:文章页的 meta 胶囊。 六个胶囊横排在 680px 里刚好,掉到 640 就开始换行。改成容器查询后,换行阈值跟着容器走,双栏布局下窄栏里的胶囊自己会折行。

第三处:表格。 宽表格在小容器里应该横向滚动,换行只会把表头挤成竖条。这条判断从视口改成了容器,表格终于能在侧栏里正常滚动了。

第四处:目录折叠。 原来是「视口 <1240 就折叠成悬浮」。问题是双栏布局下,视口 1600 但正文列只有 680,目录压根没地方放。改成看容器宽度之后逻辑才正确。

第五处:评论区。 三连输入框(昵称 / 邮箱 / 网址)横排在宽容器里,窄容器里改竖排——这条本来就要靠外层布局传信号,现在组件自己会判断。

第六处:代码块。 长代码行要不要强制换行,取决于容器宽度而不是屏幕宽度。窄容器里强制换行,宽容器里保留横向滚动。

第七处:图片九宫格。 3×3 的网格在窄容器里改成 2 列,第一张占满整行。这个判定现在只依赖容器,所以同一篇文章的九宫格在正文列和「相关文章」的小卡里能各自呈现合适的密度。

四、两个必须知道的副作用

第一,容器查询的单位是 cqw 而不是 vw

.card-title {
  font-size: clamp(16px, 4cqw, 24px);
}

4cqw 是容器宽度的 4%。这个改动让标题的缩放跟着卡片走,标题再也不会在侧栏里大到溢出。

第二,container-type: inline-size 会切断内在尺寸的传导。

这是最容易踩的一个。声明了 inline-size 之后,容器在行内方向上不再根据内容计算尺寸,而是从外部拿。也就是说:

  • 如果这个容器是 width: auto 的块级元素,没问题,它照样撑满父元素。
  • 如果它处在一个由内容决定宽度的上下文里(比如 flex 行里没写 flex-basis、或者 display: inline-block),它会塌成零宽,然后里面所有依赖容器宽度的样式全部失效,表现为「内容突然都不见了」。

我的处理办法是:只在明确知道宽度来源的元素上声明容器,并且给它们补一个 width: 100%。同时排查了所有 inline-block 的用法,换成 display: block 加显式宽度。

五、兼容性怎么处理

主流浏览器在 2023 年就都支持了,现在可以放心用。但我保留了降级路径,写法是把媒体查询作为兜底:

/* 兜底:不支持容器查询时,按视口给一个合理默认 */
.card { display: block; }

/* 支持时,改为按容器判断 */
@supports (container-type: inline-size) {
  .card-host { container-type: inline-size; }
  @container (min-width: 420px) {
    .card { display: grid; grid-template-columns: 120px 1fr; }
  }
}

@supports 把容器声明也包在里面,这样在旧浏览器里连 container-type 都不会被解析,卡片老老实实走单栏。

六、值不值得改

改完这一轮,base.css 里的媒体查询块从二十七个降到了九个,其中七个还是顶栏和侧栏这种真正的「页面级」布局。剩下那些零零碎碎的组件级断点,全部变成了容器查询。

最实在的收益不是代码少了,而是改一个组件不用再通读全文。我现在打开卡片组件的样式,看到的所有条件都只跟它自己被放进多宽的框有关——这个心智负担的下降,比省下的那几十行代码值钱得多。

如果只能记住一句话:媒体查询管页面,容器查询管组件。 分清楚这条线,样式表就不会再长成一团。

上一篇 自建博客完全指南:从一台空服务器到 kmmer.com 下一篇 博客正文布局对照:单栏 / 两栏 / 三栏

暂无评论

Ctrl + Enter 发送

还没有评论,来说点什么吧

克己笔记
Theme Kmmer Robes