一个例子看懂媒体查询
媒体查询用一个条件把 CSS 包起来。@media (min-width: 600px) 中的规则只在视口宽度至少为 600px 时生效:
预览是一个框架(frame),其中的媒体查询测量的是框架本身,而不是你的屏幕。 在桌面电脑上,它大约和这篇文章的正文栏一样宽,通常超过 600px,所以盒子是蓝色的。把浏览器窗口缩窄,直到框架小于 600px,盒子就会变成琥珀色;盒子下面那一行会实时显示框架的宽度。在手机上框架本来就很窄,所以你看到的是琥珀色的版本。
@media 语法
@media (min-width: 600px) { ... } /* one condition */
@media (min-width: 600px) and (max-width: 899px) { ... } /* both must be true */
@media (max-width: 599px), print { ... } /* either one (comma = or) */
@media not print { ... } /* negation */
@media screen and (orientation: landscape) { ... } /* media type plus a feature */
@media (width >= 600px) { ... } /* range syntax */
@media (600px <= width < 900px) { ... } /* a range with both ends */
and 两边都需要空格:(min-width: 600px)and(max-width: 900px) 是无效的,整条查询都会被忽略。范围语法(width >= 600px)在当前的 Chrome、Firefox 和 Safari 中都已支持,读起来比 min-width 更清楚,尤其是表达“到某个值为止但不包括它”的时候。
最常用的媒体特性:
| 特性 | 匹配条件 | 示例 |
|---|---|---|
min-width / width >= | 视口至少有这么宽 | (min-width: 768px) |
max-width / width <= | 视口最多有这么宽 | (max-width: 767px) |
orientation | portrait(高大于宽)或 landscape | (orientation: landscape) |
hover | 主指针能悬停(hover)或不能(none,大多数触摸屏) | (hover: hover) |
pointer | 主指针是 fine(鼠标)或 coarse(手指) | (pointer: coarse) |
prefers-color-scheme | 用户选择了 dark 或 light 主题 | (prefers-color-scheme: dark) |
prefers-reduced-motion | 用户在系统中要求减少动态效果 | (prefers-reduced-motion: reduce) |
print(一种媒体类型) | 页面被打印或另存为 PDF | @media print |
移动优先:min-width 断点
把手机布局写成基础 CSS,再添加 min-width 查询,随着空间变大改变布局。每条查询只需要描述发生变化的部分:
在较宽的桌面窗口中,预览超过 720px,所以你会看到三列;网格下面那一行会显示宽度和列数。把窗口缩窄,小于 720px 时变成两列,小于 480px 时变成一列。两条查询的顺序很重要:在宽屏上它们都成立,写在后面的那条胜出,所以 min-width 查询要从小到大排列。(max-width 查询则相反:从大到小。)
如何选择断点
并没有官方列表。下面是一些常见取值,以及它们通常划分的设备:
| 断点 | 通常对应 |
|---|---|
min-width: 640px | 横屏的大屏手机、小平板 |
min-width: 768px | 竖屏的平板 |
min-width: 1024px | 横屏的平板、小尺寸笔记本 |
min-width: 1280px | 桌面电脑 |
更好的方法是:从窄布局开始,逐渐把窗口拉宽,直到某处看起来不对劲(行太长、卡片太宽),就在那里加一个断点。大多数页面有两三个断点就够了。很多时候你根本不需要断点:grid 中的 repeat(auto-fit, minmax(200px, 1fr))、flex-wrap 和 clamp() 都能连续地自适应。
每个页面都需要 viewport meta 标签
如果 <head> 中没有这个标签,手机浏览器会把页面当成屏幕大约有 980px 宽来排版,然后再缩小显示,于是你的 min-width: 480px 查询永远成立,手机布局永远不会出现:
<meta name="viewport" content="width=device-width, initial-scale=1">
meta viewport 页面解释了其中的每一个值。
用 prefers-color-scheme 实现深色模式
prefers-color-scheme 跟随操作系统的浅色或深色设置。最简洁的用法是把颜色放在 CSS 变量里,在查询中只修改这些变量:
在这个预览中,即使你的系统设置为深色,查询也可能输出 false:Chrome 不会把系统设置传进这种沙盒框架。按钮会手动设置同样的变量,网站上的主题切换按钮就是这样工作的。:root[data-theme] 比查询里普通的 :root 优先级更高,所以读者的选择会覆盖系统设置。把示例保存为文件再打开,就能看到查询跟随你的系统设置。
脚本展示了媒体查询的另一半:matchMedia() 在 JavaScript 中运行同样的查询,返回它当前是否匹配,并在结果变化时触发 change 事件。当行为(而不仅仅是样式)取决于屏幕时就用它,例如在触摸设备上用不同的方式打开菜单。
动画也是同样的道理。把大幅度或循环播放的动画放在 prefers-reduced-motion 检查中,具体见动画页面。
容器查询:让组件自己决定
媒体查询只知道视口。同一页面中,一张卡片既可能放在宽的主栏里,也可能放在窄的侧边栏里,它需要知道的是它自己的空间。这就是容器查询:用 container-type: inline-size 标记父元素,然后用 @container 写同样的条件。
拖动灰色盒子的角来调整它的大小。无论窗口多大,卡片都会在盒子宽度达到 360px 时切换布局:
在初始的 480px 下,图片位于文字旁边。把盒子拖到窄于 376px(360px 的内容加上两侧各 8px 的内边距),图片就会移到文字上方。所有当前的主流浏览器都支持容器查询。
打印样式
@media print 只在页面被打印或另存为 PDF 时生效。隐藏导航和按钮,并使用白底黑字:
@media print {
nav, .ad, button { display: none; }
body { color: black; background: white; }
a::after { content: " (" attr(href) ")"; } /* print the URL after each link */
}
常见错误
- 没有 viewport meta 标签。 这样手机会报告一个很宽的布局视口,针对小屏幕的查询永远不会匹配。
- 查询的顺序写错。 两条成立的查询使用同一个选择器时,后写的那条胜出。
min-width查询要从小到大排列。 - 边界重叠或出现空档。
max-width: 768px和min-width: 768px在正好 768px 时都会匹配。其中一个改用767px,或者使用范围语法(width < 768px)和(width >= 768px)。 and后面没有空格。and(是无效的,整条查询会被悄悄丢弃。- 只靠缩放桌面窗口来测试。 还要在真实手机或浏览器的设备模式下检查,设备模式会应用手机的视口、像素比和触摸。
- 断点太多。 如果每个组件都需要自己的断点,可以改用 grid 的
auto-fit、flex-wrap或容器查询。
常见问题
CSS 中的媒体查询是什么?
媒体查询是一条规则,只有当关于设备或窗口的某个条件成立时,才会应用其中的 CSS。@media (min-width: 600px) { ... } 只在视口宽度至少为 600px 时应用其中的样式,同一个页面正是靠它从手机适配到桌面电脑的。
媒体查询应该用 min-width 还是 max-width?
大多数网站采用移动优先的写法:基础 CSS 针对小屏幕,再用 min-width 查询随着屏幕变宽添加布局。max-width 查询则反过来,从桌面布局开始写。两种都可以;但在同一个元素上混用两者,会在边界处造成重叠和空档。
媒体查询的标准断点是多少?
并没有官方标准。常见的取值大约是 640px、768px、1024px 和 1280px,但更好的原则是:在你自己的布局开始显得不对劲的地方加断点,并且让断点的数量尽量少。
为什么我的媒体查询在手机上不生效?
最常见的原因是缺少 viewport meta 标签。没有 <meta name="viewport" content="width=device-width, initial-scale=1"> 时,手机浏览器会假装页面大约有 980px 宽,所以针对小宽度的查询永远不会匹配。另外还要检查查询是否写在它要覆盖的规则之后。
如何为深色模式写媒体查询?
使用 @media (prefers-color-scheme: dark) { ... }。当用户的操作系统或浏览器设置为深色主题时,它就会匹配,你可以在其中覆盖颜色或 CSS 变量。
媒体查询和容器查询有什么区别?
媒体查询检测的是视口(整个窗口)。容器查询用 @container 编写,检测的是某个父元素的尺寸,这个父元素需要用 container-type: inline-size 标记。如果一个组件在同一页面中既出现在宽的位置又出现在窄的位置,就用容器查询。