图解CSS: CSS媒体查询

发布于 大漠

CSS媒体查询(CSS Media Queries)是CSS基本特性之一。它的作用就是让我们在相同的HTML基础上可以以不同的CSS实现适配各种终端的UI效果(比如,桌面端、iPad、双屏幕、折叠设备和移动手机等)。也就是说,我们可以为它们(不同的终端设备)使用相同的HTML,而不是为智能手机维护一个网站,为笔记本电脑、台式机维护另一个网站。但是它们是如何工作的,有哪些是你应该使用的呢?这就是我们接下来要和大家一起探讨的。

什么是CSS媒体查询

CSS媒体查询是CSS的一个 特定特性 ,它允许根据 媒体类型媒体特性 或者两者都有条件地应用样式。媒体查询主要用于检查屏幕尺寸并在此基础上应用CSS,但媒体查询还可以做许多其他功能强大的事情。

对于我们来说,就是针对不同的终端(PC端、iPad端和智能手机)使相同的HTML内容(结构),利用CSS媒体查询为不同的终端调用不同的CSS,从而都能实现满足不同终端的UI效果:

比如:

/* 智能手机 */
@media only screen and (max-width: 400px) {
    body {
        background-color: #F09A9D; 
    }
}

/* 平板 */
@media only screen and (min-width: 401px) and (max-width: 960px) {
    body {
        background-color: #F5CF8E;
    }
}

/* PC、平板电脑 */
@media only screen and (min-width: 961px) {
    body {
        background-color: #B2D6FF; 
    }
}

时至今日,W3C关于媒体查询有三个版本:

我用下图来描述有关于媒体查询相关的内容:

为什么要使用CSS媒体查询

正如上图所示,市场上终端设备是越来越丰富,对于Web开发者而言都是希望自己的应用在不同的终端设备上都能用户带来较好的体验,而且又不希望自己有更多额外的开发成本。

换句话说,无论在桌面设备上还是在移动设备上,用户都习惯上下滚动网站,而不是横向滚动,因此,如果用户必须横向滚动或缩小页面才能查看整个网页,那么这将给用户带来糟糕的体验。对于开发者而言,就需要为终端提供一个更好的适配方案。那么CSS媒体查询可以很好的满足这方面的需求。因为,CSS媒体查询是条件CSS特性之一,它可以对CSS样式做相应的过滤,也就是说,可以根据CSS媒体查询的条件运用不同的CSS样式。有了这些过滤器,我们可以根据设备呈现内容的特点轻松更改样式,包括显示屏类型、宽度、高度、方向甚至是分辨率。

剖析CSS媒体查询

CSS媒体查询主要包括 CSS的@规则媒体类型媒体逻辑运算符媒体特性

接下来,让我们把它们拆开看看它们实际上在做什么?

CSS媒体查询语法规则

先从CSS媒体查询的语法规则开始吧。

简单地说,上图已经描述了CSS媒体查询的语法规则。即:

<media-query> = <media-condition> | [ not | only ]? <media-type> [ and <media-condition-without-or> ]?

不过我们可以更为简单的来理解,CSS媒体查询以@media规则(CSS的@规则)开始,后面紧跟CSS的媒体查询类型、CSS媒体查询逻辑运算符和CSS媒体查询特性等,比如:

@mediia [media-type] [and | not | or] ([media-feature]) {
    /* CSS Style */
}

其中@media是不可缺少的,但其中媒体类型、媒体逻辑运算符和媒体特性(条件)是可选的。比如:

/* 包括了媒体类型、逻辑运算符和媒体条件 */
@media screen and (min-width: 400px) {
    /*...*/
}

/* 只包括了媒体类型 */
@media print {
    /*...*/
}

/* 只包括媒体条件 */
@media (min-width: 400px) {
    /*...*/
}

上面三种写法都是有效的。

或许你可能已经发现了,在@media规则后面的 媒体类型媒体逻辑运算符 和 **媒体特性(条件)**都是CSS媒体查询中可选之一,但所起的作用都是不一样的。

通常一点说,媒体查询包含一个可选的媒体类型和媒体特性表达式(零个或多个),它最终会被解析为true(符合条件规则)或false(不符合条件规则)。如果媒体查询中指定的媒体类型匹配展示文档所使用的设备类型,并且所有的表达式的值都为true,那么该媒体查询的结果为true

@media

@media是CSS中@规则中的一个。它的主要作用是用来指定媒体的特性,也就是说,客户端可以根据@media提供相关条件运用不同的样式规则,当客户端匹配@media提供的相关规则时,则会运用对应的CSS样式规则。

媒体类型(Media Types)

媒体类型主要用于 描述浏览器运行的设备类型(终端类型),从Media Queries Level 4之后放弃了很多媒体类型,目前留下的主要有四种媒体类型。

  • all: 它是媒体查询中默认的媒体类型,指的是匹配所有设备
  • print:适用于在打印预览模式下在屏幕上查看分页材料和文档(有关特定于这些格式问题的信息,请参阅 分页媒体part1part2
  • screen:主要用于屏幕
  • speech:主要用于语音合成器(比如屏幕阅读器)。这将取代现在不赞成使用的aural类型

为了完整起见,下面这些已被放弃(或已放弃)的媒体类型,但规范还是建议浏览器能识别它们:aural(已经被speech替代)、ttyprojectionhandheldbrailleembossed

比如说,我们有些样式仅用于打印预览模式下,可以像下面这样写:

@media print  {
    body {
        background-color: #fff;
        color: #000
    }
}

如果仅用于屏幕上的样式,可以像下面这样写:

@media only screen {
    body {
        background-color: #fff;
        color: #444;
    }
}

媒体特性(Media Features)

媒体特性(Media Features)是CSS媒体查询中最主要的部分,对您的设计有很大的影响。媒体特性可以做很多事情。

媒体特性描述了用户代理(User Agent)、输出设备或是浏览环境的具体特征。媒体特性表达式是完全可选的,它负责测试这些特性或特征是否存在、值为多少。每条媒体特性表达式都必须用小括号(())包裹起来。

换句话说,一旦我们定义了想要匹配的媒体类型,我们就 可以开始定义我们想要的媒体特性(即匹配的功能)。我们在前面的示例中展示过:

@media only screen and (max-width: 400px) {
    body {
        background-color: #F09A9D; 
    }
}

其中screen媒体类型,小括号()及括号里的max-width: 400pxmax-widthmin-width,还有其他,带有特性的值)被称为媒体特性。

或许你也已经发现了,从前面的媒体查询相关的知识体系的图谱中你可能看到了,媒体特性的内容是非常丰富的,也是最为CSS媒体查询中最为重要的一部分:

正如上图所示,从CSS 2.1,到CSS Media Queries Level 4,再到CSS Media Queries Level 5,媒体特性主要分为 视窗和页面媒体特性显示媒体特性颜色媒体特性交互媒体特性作用于视频媒体特性脚本媒体特性用户喜好媒体特性双屏幕或可折叠屏幕媒体特性以及一些废弃的媒体特性。这几大类媒体特性中都含有已客户端支持的媒体特性,也有还未得到支持的媒体特性。

接下来,我们来分别看看这些媒体特性,以及他们所起的作用。

视窗和页面媒体特性

视窗和页面媒体特性(Viewport/Page Characteristics)主要有:

媒体特性名称 媒体特性值 **是否接受mminmax的前缀 ** 描述 备注
width <length> 定义视窗的宽度(包括滚动条大小) 它的值可以是一个确定的数值(比如320px),也可以是min-widthmax-width指定的一个范围值(比如(min-width: 30em))
height <length> 定义视窗的高度(包括滚动条大小) 具体使用和width类似
aspect-ratio <ration> 定义视窗的宽高比  
orientation portraitlandscape 定义屏幕的方向,取决于设备的旋转方式
overflow-block scrolloptional-pagedpaged 检测设备如何处理在块轴方向溢出视窗的内容 CSS Media Queries Level 4,CSS Media Queries Level 5将其纳入到显示媒体特性中
overflow-inline scrollnone 检测设备如何处理在内联轴方向溢出视窗的内容 CSS Media Queries Level 4,CSS Media Queries Level 5将其纳入到显示媒体特性中

上面表格中所列的媒体特性,可能大家常见到的或常用的是widthheightorientation,有的时候也能看到orientation的身影,但overflow-blockoverflow-inline不怎么使用。

widthheight

你可以检查终端视窗的精确宽度(width)或高度(height),但也可以是一个范围值,比如min-widthmax-widthmin-heightmax-height。不过widthheight这种精确值并不怎么使用,更有用(或更常用)的是min-*max-*,比如:

body {
    background-color: #0EAD69;
}

@media screen and (max-width: 1600px) {
    body {
        background-color: #3BCEAC;
    }
}

@media screen and (max-width: 1280px) {
    body {
        background-color: #FFD23F;
    }
}

@media screen and (max-width: 960px) {
    body {
        background-color: #EE4266;
    }
}

@media screen and (max-width: 600px) {
    body {
        background-color: #540D6E;
    }
}

在CSS媒体查询特性中min-*max-*是非常可见的,有关于它们更详细的介绍将在后面的章节中阐述。

orientationaspect-ratio

对于很多手持移动终端而言,他们有分横屏(Landscape)和 竖屏(Portrait):

在CSS媒体特性中通过orientation可以直接用来区分它们:

  • portrait:竖屏,屏幕视窗高度大于宽度
  • landscape:横屏,屏幕视窗宽度大于高度

如果你希望在横竖屏有着不同的布局方式,可以像下面这样写CSS:

@media screen and (orientation: portrait) {
    body {
        background-color: #ffd23f;
    }
}

@media screen and (orientation: landscape) {
    body {
        background-color: #ee4266;
    }
}

正如上面示例所示,当浏览器视窗高度大宽度时,竖屏(portrait)就会触发;类似的,如果视宽宽度大于高度,那么横屏(landscape)就会被触发。

其实,orientation特性是用来描述视窗宽度和高度两者之间的关系。在CSS媒体查询特性中,除了使用orientation媒体特性来描述视窗宽高之间关系之外,还可以使用aspect-ratio来描述。

aspect-ratio可以让你检查浏览器视窗宽度和高度是否有一定的比例。其中第一个数字表示视窗宽度,第二个数值表示视窗高度,并且两个数值之间有一个/分隔符。比如:

/* 视窗宽度和高度比率是1:1 */
@media (aspect-ratio: 1/1) {
    body {
        background-color: #ffd23f;
    }
}

/* 视窗宽度和高度比率是4:3 */
@media (aspect-ratio: 16/9) {
    body {
        background-color: #ee4266;
    }
}

另外,aspect-ratiowidth有点类似,除了使用精确的宽高比值之外,还可以使用min-aspect-ratiomax-aspect-ratio用来指定一个范围值。例如,使用min-aspect-ratio可以检测屏幕的宽度比高度更大:

@media (min-aspect-ratio: 1/1) {
    body {
        background-color: #ffd23f;
    }
}

@media (min-aspect-ratio: 4/3) {
    body {
        background-color: #ee4266;
    }
}

效果如下:

显示质量媒体特性

显示质量媒体特性(Display Quality)主要有:

媒体特性名称 媒体特性值 是否接受minmax前缀 描述 备注
resolution <resolution>,infinite 输出设备的像素密度(分辨率)  
scan interlace, progressive 输出设备的扫描过程(适用于电视等)  
grid <mq-boolean> 输出设备使用网格屏幕还是点阵屏幕? Media Queries Level 5
update none, slow, fast 输出设备更新内容的渲染结果的频率 Media Queries Level 4
environment-blending opaque, additive, subtractive 一种测定设备外部环境的方法,例如昏暗或过于明亮的地方 Media Queries Level 5,也被称为环境媒体查询特性
display-mode fullscreen, standalone, minimal-ui, browser 应用程序的显示模式,如Web App的Manifest中的display成员所指定 Web App Manifest

这里我们主要介绍resolutiongridenvironment-blendingdisplay-mode几个媒体特性。

resolution

使用resolution媒体特性可以测试显示像素密度。简单地说,我们可以根据终端设备屏幕的像素密度做为媒体查询的条件。因为自从苹果公司发布视网膜(Retina Display)之后,高像素密度的屏幕就越来越多,在Web开发中就有可能需要为不同像素密度的屏幕提供不同的样式或资源,特别是背景图片的使用。

屏幕像素密度指的就是 window.devicePixelRatio

用来描述输出设备的分辨率主要有:

  • dpi:点每英寸
  • dpcm:点每厘米
  • dppx:点每像素

在CSS中,一个像素总是96dpi,所以1dppx是常规的屏幕分辨率,2dppx是视网膜屏幕。

@media (resolution: 150dpi) {
    p {
        color: red;
    }
}

同样的,在使用resolution时也要像width类似,添加min-*max-*做前缀:

@media (min-resolution: 72dpi) {
    p {
        text-decoration: underline;
    }
}

@media (max-resolution: 300dpi) {
    p {
        background: yellow;
    }
}

@media only screen and (min-resolution: 1.5dppx) {
    p {
        background: #f36;
    }
}

另外还可以使用x来替代dppx,比如1倍的我们可使用1x,两倍的我们可以使用2x

@media (min-resolution: 1x) {
    body {
        background-color: #f36;
    }
}

@media (min-resolution: 2x) {
    body {
        background-color: #09f
    }
}

@media (min-resolution: 3x) {
    body {
        background-color: #fac;
    }
}

不过Safari还不支持resolution媒体特性,但它有一个和该特性等同的私有特性,即-webkit-device-pixel-ratio(也可以带min-*max-*前缀,比如-webkit-min-device-pixel-ratio-webkit-max-device-pixel-ratio),它接受一个没有单位的数字:

@media (-webkit-min-device-pixel-ratio: 1) {
    body {
        background-color: #f36;
    }
}

@media (-webkit-min-device-pixel-ratio: 2) {
    body {
        background-color: #09f;
    }
}

@media (-webkit-min-device-pixel-ratio: 3) {
    body {
        background-color: #fac;
    }
}

还可以使用,分隔符将它们合并在一起,比如:

@media (-webkit-min-device-pixel-ratio: 1), (min-resolution: 1x) {
    body {
        background-color: #f36;
    }
}

@media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 2x) {
    body {
        background-color: #09f;
    }
}

@media (-webkit-min-device-pixel-ratio: 3), (min-resolution: 3x) {
    body {
        background-color: #fac;
    }
}
grid

grid媒体特性主要用于查询输出设备是网格(Grid)还是位图(Bitmap)。如果输出设备是基于网格的(例如,tty终端,或只有一种固定字体的电话显示器),那它的值为1。否则,值为0(比如,现在大多数现代电脑和智能手机都有基于位图的屏幕)。

grid的值是<mq-boolean>,而<mq-boolean>是一个<integer>,值只有两个,即10

/* 普通屏幕,比如电脑和智能手机设备 */
@media (grid: 0) {
    body {
        backround-color: #09f;
    }
}

/* 位图设备,比如tty终端 */
@media (grid: 1) {
    body {
        background-color: #ccc;
    }
}

注意,这里的grid和我们平时所说的网格布局完全是两个不同的东东。

display-mode

display-mode并不隶属于CSS媒体查询模块,它是Web App Manifest中的一个特性。

虽然display-mode不是CSS媒体查询模块的媒体特性,但是它同样可以被运用于@media的媒体查询特性中。而且display-mode是用来优化用户体验的一个特性。它可以用来检测应用程序的显示模式。简单地来说,可以使用它在从URL打开站点和从桌面图标打开站点,但给用户提供的是一致的体验。拿一个示例来说,你的站点是一个关于游戏或视频类的,但页面上可能不仅仅只有游戏或视频,但当用户切换到全屏模式时,你只想给用户显示游戏或视频。对于这样的应用场景,那么display-mode就可以做到。

display-mode接受四个值:

  • fullscreen:页面全屏显示,整个屏幕不会有其他应用可见。如果没有定义fullscreen,浏览器将应用standdalone中定义的样式
  • standalone:页面没有全屏显示,它看起来像一个独立的(桌面)应用程序,如果没有定义standalone,浏览器将应用minimal-ui中定义的样式。在这种模式下,用户代理将排除标准的浏览器UI元素,如URL栏,但可以包括其他系统UI元素,如状态栏和/或系统后退按钮
  • minimal-ui:页面显示在它自己的窗口中,但是浏览器仍然会显示一些浏览器的控制导航(有点类似新窗口打开),如果没有定义minimal-ui,浏览器将应用browser中定义的样式。这种模式与独立模式类似,但为最终用户提供了一些访问用于控制导航的最小UI元素集的方法(例如,后退、前进、重载,以及查看文档地址的一些方法)。用户代理可以包含其他特定于平台的UI元素,比如“共享”和“打印”按钮或者平台和用户代理上常见的任何东西
  • browser:这是display-mode的默认模式,你的页面将显示在一个常规的浏览器窗口中。这种模式使用特定于平台的约定来打开用户代理中的超链接(例如,在浏览器选项卡或新窗口中)来打开web应用程序

这里特别将display-mode取值为fullscreen单独拿出来和大家聊一下。主要是因为,在W3C规范中对于全屏有另一套规范,即 Fullscreen API。虽然都是全屏模式显示,但它们是完全独立的东东,两者之间没有任意交集。全屏显示模式(display-mode: fullscreen)影响浏览器窗口的全屏状态,而Fullscreen API对视窗中包含的元素进行操作。因此,Web应用可以将其显示模式设置为全屏,而document.fullScreenElement将返回nullfullscreenEnabled返回false

另外,全屏显示模式通过CSS媒体查询特性表示Web应用程序的显式模式。此媒体特性适用于顶级浏览上下文和任何子浏览上下文。子浏览上下文反映了顶级浏览上下文的显示模式。

我们来看一个示例,当一个HTML元素被放入到全屏幕元素堆栈中时,:fullscreen伪类会独家匹配。但是,使用Fullscreen API对元素调用requestFullscreen()方法的副作用是,浏览器窗口可以在系统级别进入全屏模式。在这种情况下,:fullscreendisplay-mode:fullscreen将匹配。

在一些平台上,用户可以在没有Fullscreen API的帮助下将浏览器窗口放到全屏。当这种情况发生时,:fullscreen伪类将不匹配,但display-mode:fullscreen将匹配。

.box {
    width: 200px;
    height: 200px;
    background-color: #09f;
    transition: all 0.2s linear;
}

@media all and (display-mode: fullscreen) {
    .box {
        background-color: #f36;
        border-radius: 12px;
    }
}

.box:fullscreen {
    border-radius: 50%;
    background-color: #09afea;
}

display-mode: fullscreen匹配前后的效果如下(你也可以尝试在上面示例中点击“Go Fullscreen”按钮查看效果):

environment-blending

environment-blending媒体特性用于查询用户显示的特征,从而调整页面样式。开发者可以根据显示技术选择调整页面的视觉效果或布局,以增加吸引力或提高可读性。该特性接受三个值:

  • opaque:文档是在不透明的媒体上渲染,比如传统的显示器或纸张。黑色是暗的,白色是100%的光
  • additive:显示器混合画布的颜色与现实世界使用的添加剂混合。黑色是完全透明的,白色是100%的光,例如汽车上的平视显示器
  • subtractive:该显示器混合了画布的颜色与现实世界使用减色法混合。白色是完全透明的,而深色的对比度最大。例如,嵌入浴室镜子中的液晶显示屏

比如下面这个示例:

body { 
    background-color: white; 
}

p { 
    color: black; 
}

@media(environment-blending: additive) {
    body { 
        background-color: black; 
    }

    p { 
        color: white; 
        font-size: 16px; 
        font-weight: 1000; 
    }
}

颜色媒体特性

颜色媒体特性主要包括:

媒体特性名称 媒体特性值 是否接受minmax前缀 描述 备注
color <integer> 输出设备每个像素的比特值,常见的有 81632位。如果设备不支持输出彩色,则该值为 0  
color-index <integer> 输出设备的颜色查询表中的条目数量,如果设备不使用颜色查询表,则该值为 0  
monochrome <integer> 输出设备单色帧缓冲区中每个像素的位深度。如果设备并非黑白屏幕,则该值为 0  
color-gamut srgb, p3, rec2020 用户代理和输出设备大致程度上支持的色域 Media Queries Level 4
dynamic-range standard, high 浏览器和用户设备的视频平面所支持的亮度、颜色深度和对比度的组合  
inverted-colors inverted, none 用户代理或者底层操作系统是否反转了颜色 Media Queries Level 5

估计有关于颜色媒体查询特性在项目几乎不怎么被使用。但我在这里着重和大家聊一下inverted-colorscolor-gamut,因为它们可以提高用户体验。

inverted-colors

在苹果公司推出DarkMode之前,有一些安卓设备就有类似的功能,只不过他们被称为反转模式或者说是夜间模式:

但有些安卓设备中Dark Mode的效果实际就是粗暴的颜色反转或滤镜的一个效果:

不过有些用户可能喜欢这样的效果。它看起来很整洁,黑白之间的对比度更明显:

inverted-colors媒体查询特性让用户适应这些怪癖。该媒体查询特性可以接受两个值:

  • none:颜色显示正常
  • inverted:显示区域内的所有像素都被倒置了

inverted-colors是一个布尔值选项,但也可以直接跳过这个值,写成这样:

.text {
    font-size: 24px;
}

@media screen and (inverted-colors: inverted) {
    .text {
        font-size: 36px;
    }
}

如果你是使用苹果电脑的话,同样可以在系统级别做这方面设置:

也可以说,inverted-colors是根据用户喜设置来做查询。

color-gamut

到目前为止,Web上的颜色仅限于sRGB颜色空间(色域),但是现代终端屏幕可以显示更多的颜色,比如p3的颜色空间。用色域可以测试设备是否有这样丰富的颜色空间。

对于color-gamut有三个值可选:

  • srgbsRGB颜色空间是指由红、绿、蓝三组值表示,它们标识了sRGB颜色空间中的一个点,这也是我们熟悉的颜色空间
  • p3:它是现代iPhone所使用的,通常被称为“宽色域”(或“丰富色域”),指的是Display-p3Display-P3颜色空间颜色要比sRGB颜色空间中的颜色更鲜艳,也可以说Display-p3sRGB的一个超集,大约要大35%
  • rec2020:这是目前可用的最大的色彩空间

也就是说,如果一个用户有一个更宽的色域显示,你可以在那个颜色空间使用,它们比sRGB更丰富,更生动:

目前p3(也就是Display-p3)支持的终端设备不多,但我们可以使用CSS媒体查询来做判断:

/* 支持sRGB颜色空间 */
p {
    color: rgb(255 0 0);
}

/* 支持p3(Display-p3)颜色空间 */
@media (color-gamut: p3) {
    p {
        color: color(display-p3 1 0 0);
    }
}

对于这些新颜色,有三个新的CSS函数lab()lch()color()。有关于这方面更详细的介绍可以阅读《CSS 颜色》一文。

交互媒体特性

交互媒体特性主要包括:

媒体特性名称 媒体特性值 是否接受minmax前缀 描述 备注
pointer coarse, fine, none 主要输入机制是一个指针设备吗?如果是,它的精度如何? Media Queries Level 4
hover hover, none 主要输入模式是否允许用户在元素上悬停 Media Queries Level 4
any-pointer coarse, fine, none 是否有任何可用的输入机制允许用户(将鼠标等)悬停在元素上? Media Queries Level 4
any-hover hover, none 可用的输入机制中是否有任何指针设备,如果有,它的精度如何? Media Queries Level 4

对于众多Web开发者而言,在PC端我们可以通过:hover伪类选择器给鼠标的悬浮状态添加不同的效果:

.box {
    width: 50vh;
    height: 50vh;
    background-color: #09f;
    cursor: pointer;
    transition: all .28s ease;
}

.box:hover {
    border-radius: 14px;
    background-color: #f90;
}

但在很多移动设备(比如智能手持设备)中并没有鼠标悬浮状态:

换句话说,我们现在有了更多不同的输入(input)或指针(point)设备。鼠标指针虽然还存在,但我们也有触控,Will遥控器等外部控制器,甚至还有AR手检测功能。

有些事情用鼠标很容易做到,但用控制设备却很难或不可能做到,比如命中小目标或甚至是悬浮在某目标上,正如前面提到的鼠标悬浮在元素上。不过,有了交互媒体特性,那么你就可以在相应的设备上采用适应的方式。

.box {
    width: 50vh;
    height: 50vh;
    background-color: #09f;
    cursor: pointer;
    transition: all 0.28s ease;
}

.box:hover {
    border-radius: 14px;
    background-color: #f90;
}

/* 触屏设备效果 */
@media (hover: none) and (pointer: coarse) {
    .box {
        border-radius: 14px;
        background-color: #f36;
    }
}

像iPhone这样的移动手持设备虽然不支持鼠标悬浮,但可以使用触控笔或其他精确定位设备。针对于这样的场景,我们可以设置hover:none,并且pointer:fine,比如:

@media (hover: none) and (pointer: fine) {
    .box {
        border: 5px solid #9f9;
    }
}

你也可以在一个悬停的设备上(hover: hover),但是有一个粗糙的指针(pointer: coarse)为元素指定不同样式:

@media (hover: hover) and (pointer: coarse) {
    .box {
        border-radius: 50%;
    }
}

另外,像Will控制器和Kinect等设备允许鼠标悬停(hover: hover),但指针很粗(pointer: coarse)。它们可以让你指着东西,但没有很高的精确度。你会想要足够大的目标,你可以添加悬浮效果作为他们指向的指示。

另外,有些设备是带有鼠标、触控版的设备。比如Mac电脑,iPad等:

对于这样的设备,我们可以这样来给元素指定样式:

@media (hover: hover) and (pointer: fine) {
    .box {
        border-radius: 50%;
        border: 5px solid #89f;
        background-color: orange;
    }
}

除了pointerhover还有any-hoverany-pointer。其中pointerhover媒体特性为你提供有关主指向设备的信息。但是,如果你同时拥有一个粗点和一个细点设备,比如带有触控笔的触摸屏。用户在这种设备上,可能会有一支手写笔,主要的指向设备仍然是触摸屏。对于这些情况,你可以使用any-hoverany-pointer。这将测试是否存在任何匹配标准的指向设备。

@Patrick H. Lauke在《Interaction Media Features and Their Potential (for Incorrect Assumptions)》教程中详细的阐述了hoverpointerany-hoverany-pointer几个媒体特性的现状以及将来的潜力。如果你想彻底的了解这几个媒体特性的话,建议你花时间阅读这篇文章。

从@Patrick H. Lauke的教程中可以获知,这几个特性在不同设备中支持度也不一样:

媒体特性 触摸屏幕 触摸屏幕+鼠标 台式机或笔记本电脑 台式机或笔记本电脑 + 触摸屏
pointer:coarse
pointer:fine
hover:none
hover:hover
any-pointer:coarse
any-pointer:fine
any-hover:none
any-hover:hover

根据规范,浏览器应该根据用户环境的变化重新评估媒体查询。这意味着pointerhoverany-pointerany-hover媒体特性可以在任何时候动态更改。例如,在移动平板设备上添加或移除蓝牙鼠标将会触发any-pointerany-hover媒体特性状态的改变。

视频前缀媒体特性

视频前缀媒体特性是CSS Media Level 5中引入的。一些用户代理(包括许多电视)将视频和图形渲染在两个独立的平面(双平面)中,具有不同的屏幕特征。视频前缀媒体特性可以为其描述视频平面。

媒体特性名称 媒体特性值 是否接受minmax前缀 描述 备注
video-color-gamut srgb, p3, rec2020 描述浏览器和用户设备的视频平面支持的颜色的大致范围 Media Queries Level 5
video-dynamic-range standard, high 浏览器和用户设备的视频平面所支持的亮度、颜色深度和对比度的组合 Media Queries Level 5
video-width <length> 目标显示器的视频平面区域的宽度 Media Queries Level 5
video-height <length> 目标显示器的视频平面区域的高度 Media Queries Level 5
video-resolution <resolution>, inifinite 视频的分辨率的平面区域有针对性的显示 Media Queries Level 5

脚本媒体特性

使用脚本媒体特性(scripting)可以测试JavaScript是否可用。它有三个值可用:

  • none:JavaScript不可用
  • initial-only:JavaScript仅在页面加载期间可用,但在页面加载之后不可用
  • enabled:JavaScript可用

用户喜好媒体特性

用户喜好(偏好)是指根据用户自己个人喜好在设备系统中做一些设置。比如在Mac电脑上,根据自己喜好做一些设置:

现在在很多手持智能终端都给用户提供了一些设置。

CSS Media Queries Level 5中提供了一些用户喜好的媒体特性,这些特性可以识别出用户的喜好设置,从而调整Web应用的样式风格。也可以说,这些媒体特性主要是用来优化用户体验的。

到目前为止,规范中提供了六个有关于用户喜好的媒体特性:

媒体特性名称 媒体特性值 是否接受minmax前缀 描述 备注
prefers-reduced-motion no-preference, reduce 用户是否希望页面上出现更少的动态效果 Media Queries Level 5
prefers-reduced-transparency no-preference, reduce 用户是否倾向于选择更低的透明度 Media Queries Level 5
prefers-contrast no-preference, high, low, forced 探测用户是否有向系统要求提高或降低相近颜色之间的对比度 Media Queries Level 5
prefers-color-scheme light, dark 探测用户倾向于选择亮色还是暗色的配色方案 Media Queries Level 5
forced-colors active, none 检测是用户代理否限制调色板 Media Queries Level 5
prefers-reduced-data no-preference, reduce 检测用户是否愿意为要渲染的页面使用更少的网络流量 Media Queries Level 5
light-level dim, normal, washed 环境光亮度 Media Queries Level 5,该特性也被称为“环境媒体查询特性”

这些特性都是围绕着Web可访问性,即给用户提供更好的用户体验。

prefers-reduced-motion

Web页面或应用难免少不了用一些动效来点缀,但有些用户不喜欢这些动画效果,甚至对于少数用户来说,这些动效会让他们身体不适。这就是为什么现在大多数设备都支持一种方法让用户根据自己的喜好来做设置。

使用prefers-reduced-motion媒体查询用于检测用户的系统是否被开启了动画减弱功能。该媒体查询特性接受两个值:

  • no-preference:用户未修改系统动画相关特性
  • reduce:这个值意味着用户修改了系统设置,将动画效果最小化,最好所有的不必要的移动都能被移除

下面的例子将会展示一组令人心烦的动画,不过当你开启了系统的“减少运动”后就能看到动画减弱的效果了。

.pulse {
    animation: pulse 2s infinite;
}

@media screen and (prefers-reduced-motion: reduce) {
    .pulse {
        animation: none;
    }
}

上面我们看到的示例演示的是prefers-reduced-motion媒体特性如何让animation停止,其实CSS的transition也可以实现动画效果,加上并不是所有设备对动效都有一个很好的性能支持(毕竟动效是较耗性能的),因此,我们可以像下面这样来写CSS:

@media screen and (prefers-reduced-motion: reduce), (update: slow) {
    * {
        animation-duration: 0.001ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.001ms !important;
    }
}

这段代码强制所有使用动画持续时间或过渡持续时间声明的动画以人眼无法察觉的速度结束。当用户要求减少动画体验,或者设备屏幕刷新率较低时,比如廉价智能手机,它就能工作。

上面的代码对于CSS的animationtransition制作的动效有很好的控制,只要用户在设置中将减少动画选项选中,那么动效就会停止。但Web上还有很多动画效果,比如JavaScript制作的动画,Canvas,SVG中的动画,上面的代码就无法控制了。不过我们可以使用媒体查询的JavaScript API,即 window.matchMedia 在JavaScript中对媒体查询进行操作。

function getPrefersReducedMotion() {
    const QUERY = '(prefers-reduced-motion: no-preference)';
    const mediaQueryList = window.matchMedia(QUERY);
    const prefersReducedMotion = !mediaQueryList.matches;
    return prefersReducedMotion;
}

正如上图所示,如果用户在设置中没有勾选“减少动态效果”复选框,那么mediaQueryList.matches返回true。记住,我们在代码中检测的是prefers-reduced-motionno-preference。如果用户勾选“减少动态效果”复选框,那么no-preference将为false。因此,为了确定用户是否勾选“减少动态效果”复选框,我们使用!mediaQueryList.matches来反转这个布尔值。

另外,也可以使用事件监听来处理:

const QUERY = '(prefers-reduced-motion: no-preference)';
const mediaQueryList = window.matchMedia(QUERY);

const listener = event => {
    const getPrefersReducedMotion = getPrefersReducedMotion();
};

mediaQueryList.addListener(listener);
mediaQueryList.removeListener(listener);

当用户在操作系统中切换“减少动态效果”复选框时,该侦听器将触发。我们想要监听这个事件,因为我们想要在用户勾选“减少动态效果”复选框时立即终止动画,即使页面已经加载或动画正在进行中。

如果你希望在Reack中使用的话,可以自定义一个“减少动态效果”相关的钩子函数:

const QUERY = '(prefers-reduced-motion: no-preference)';

const getInitialState = () => (
    !window.matchMedia(QUERY).matches
);

function usePrefersReducedMotion() {
    const [
        prefersReducedMotion,
        setPrefersReducedMotion
    ] = React.useState(getInitialState);

    React.useEffect(() => {
        const mediaQueryList = window.matchMedia(QUERY);

        const listener = event => {
            setPrefersReducedMotion(!event.matches);
        };

        mediaQueryList.addListener(listener);

        return () => {
            mediaQueryList.removeListener(listener);
        };
    }, []);
    return prefersReducedMotion;
}
prefers-contrast

prefers-contrast媒体查询主要用于检测用户是否要求系统增加或减少相邻颜色之间的对比度。比如一些喜欢阅读电子书的用户,在阅读与文本背景对比度相差不大的文本时会遇到困难,他们更喜欢较大的对比度,利于阅读。

该媒体查询接受三个属性值:

  • no-preference:用户未向系统显式设置任何首选项。此关键字值在布尔上下文中计算为false
  • high:用户更喜欢对比度较高的界面
  • low:用户更喜欢对比度较低的界面

比如像下面这个示例:

.contrast {
    background-color: #0958d8;
    color: #fff;
}

@media (prefers-contrast: high) {
    .contrast {
        background-color: #0a0db7;
    }
}
prefers-reduced-transparency

prefers-reduced-transparency媒体特性用于检测用户是否要求系统最小化它所使用的透明或半透明层效果。换句话说,一些操作系统提供了减少系统使用透明或半透明分层效果的选项。该媒体查询特性接受的值:

  • no-preference:用户未向系统显式设置任何首选项
  • reduce:用户更喜欢最小化透明或半透明层效果的界面

用户可以使用这个来表示他们更喜欢在纯色上看东西,这通常是由于视觉障碍导致难以阅读文本,例如背景图像。但是它了可以帮助那些有阅读障碍或者注意力不集中的人更容易地阅读你的内容。

注意,它并没有说不透明。

在实际中,你可以像下面这样使用:

.transparency { 
    opacity: 0.5; 
} 

@media (prefers-reduced-transparency: reduce) { 
    .transparency { 
        opacity: 1; 
    } 
}

另外,想要更少的透明度和想要更多的对比是不一样的,不应该被混为一谈。prefers-reduced-transparency是由于不同的原因而存在的,并且可以得到不同的补偿。

prefers-color-scheme

你可能知道了,macOS系统和iOS13之后,苹果设备具备Dark Mode效果,就是用户可以根据自己的喜好来选择系统提供的色系:

在CSS中的媒体查询中提供了prefers-color-scheme媒体查询。该媒体查询特性可以让你对用户是否打开了设备上DarkMode来做出反应。换句话说,给Web页面或应用添加DarkMode只需要几行代码即可。

prefers-color-scheme用于检测用户的系统主题是浅色或深色,它对应有三个值:

  • no-preference:表示用户未指定操作系统主题。其作为布尔值时以false输出
  • light:表示用户的操作系统是浅色主题
  • dark:表示用户的操作系统是深色主题

我们可以像下面这样来使用prefers-color-scheme

/* Light theme */
:root {
    --c-text: #333;
    --c-background: #fff;
}

body {
    color: var(--c-text);
    background-color: var(--c-background);
}

/* Dark theme */
@media (prefers-color-scheme: dark) {
    :root {
        --c-text: #fff;
        --c-background: #333;
    } 
}

如果你对Dark Mode方面感兴趣,想了解这方面更多的知识,可以花点时间阅读下面这几篇文章:

forced-colors

forced-colors特性用于检测用户代理是否启用了强制颜色模式,该模式强制在页面上使用用户选择的有限颜色调色板。该特性支持两个属性值:

  • none:强制色彩模式不活跃,页面的颜色没有被限制在一个有限的调色板中
  • active:强制色彩模式已开启。UA将通过CSS系统颜色关键字为作者提供调色板,并在适当的情况下触发prefers-color-scheme的值,以便作者可以调整页面

比如下面这个示例:

.colors { 
    background-color: red; 
    color: grey; 
} 

@media (forced-colors: active) { 
    .colors { 
        background-color: white; 
        color: black; 
    } 
}
prefers-reduced-data

不是每个人都能幸运地拥有快速、可靠或无限的数据(流量)套餐。

浏览器可以在头部发送保存数据(Save-data: on),然后Web服务器可以选择发送较小的图片和视频,并禁用任何形式的轮询或预加载。尽管打开发送头信息的功能隐藏在设置中(在移动设备上)或者需要第三方插件(在桌面设备上),但还是有很多人最终使用了这个头信息。

不幸的是,在Web服务器级别上处理这个问题通常很困难,要么是因为您缺少访问权限,要么是因为使其工作的配置要求太复杂了。这是不幸的,因为它有潜在的影响力。

庆幸的是,CSS的媒体特性prefers-reduced-data来做这件事情。

.image { 
    background-image: url("images/heavy.jpg"); 
} 

@media (prefers-reduced-data: reduce) { 
    .image { 
        background-image: url("images/light.avif"); 
    } 
}

当用户在设备上开启了“Low Data Mode”(低数据模式),会加载占流量更低的light.avif图像,可以帮助iPhone上的应用程序减少网络数据的使用:

light-level

light-level媒体查询特性可以根据用户是在白天还是晚上来调整Web页面或应用的样式。该特性接受三个值:

  • dim:该设备是在昏暗的环境中使用的。在这种环境中,过高的对比度和亮度会分散读者的注意力或让用户阅读时眼睛不舒服
  • normal:该设备是在一个环境中使用的光水平在理想的范围内,这并不需要任何特定的调整
  • washed:该设备是在非常明亮的环境下使用的,导致屏幕被洗掉,难以阅读

我们到时可以像下面这样使用:

@media (light-level: normal) { 
    p { 
        background: url("texture.jpg"); 
        color: #333; 
    } 
} 

@media (light-level: dim) { 
    p { 
        background: #222; 
        color: #ccc; 
    } 
} 

@media (light-level: washed) { 
    p { 
        background: white; 
        color: black; 
        font-size: 2em; 
    } 
}

有关于这几个优化用户体验相关的CSS媒体特性更详细的介绍,还可以阅读《CSS媒体查询新特性》一文。

双屏幕和可折叠媒体查询特性

随着技术不断的发展,我们所面对的终端个性化越来越强,比如现在市场上已有或将有的双屏幕和可折叠屏幕设备:

作为Web开发者,我们终究有一天需要面对这些终端的适配处理。到目前为止,CSS世界具备处理方面适配的能力,即使用screen-spanning媒体查询条件和env(fold-left)env(fold-top)env(fold-height)env(fold-width)环境变量:

CSS的screen-spanning可以被指定为一个值,该值可以描述设备具有的折叠(或铰链)数量及其姿态。如果该设备不是可折叠设备,则值为none。如果它是可折叠的,它可以接受以下两个值中的一个:

  • single-fold-vertical:屏幕是水平的,布局视图跨越单个折叠(两个屏幕)并且折叠姿势是垂直时(分左右两边),这个值是匹配的
  • single-fold-horizontal:屏幕是垂直的,布局视图跨越单个折叠(两个屏幕)并且折叠姿势是水平时(分上下),这个值是匹配的

比如下面这个示例:

/* 智能手机 */
@media (max-width: 420px) {
    body {
        background-color: yellow;
    }
}

/* 双屏幕 */
@media (screen-spanning: single-fold-vertical), (screen-spanning: single-fold-horizontal) {
    body {
        background-color: green;
    }
}

借助FlexboxGrid布局,可以让我们在双屏幕或可折叠屏幕中布局变得更简单一些:

body {
    height: 100vh;
    display: flex;
}

.article {
    flex: 0 0 env(fold-left);
    margin-inline-end: env(fold-width);
    overflow-y: scroll;
}

.figure {
    flex: 1;
    margin: 0;
    overflow: hidden;
}

.figure img {
    height: 100%;
}

注意,示例中的margin-inline-end是CSS逻辑属性,如果你对这方面知识感兴趣的话,可以点击这里进入CSS逻辑属性的世界

双屏幕和可折叠屏幕对于很多Web开发者而言都是新的课题,如果你想深入的了解这方面的知识体系,建议你花一些时间阅读下面这些文章:

自定义媒体查询特性

我们可能会在同一个文档中多个地方使用相同的媒体查询,多次重复使用相同的媒体查询会造成编辑风险。为了让Web开发者避免这种风险的存在,CSS媒体查询提供了另一种媒体特性,即 自定义媒体查询。特别是对于更长的,更复杂的媒体查询,自定义媒体查询可以简单为媒体查询命名别名。通过这种方式,可以将在多个地方使用的媒体查询分配给可在任何地方使用的自定义媒体查询,同时Web开发者在编辑媒体查询时只需要修改一行代码。

在介绍自定义媒体查询特性之前,我们简单的回顾一下CSS的自定义属性。

了解过CSS自定义属性的同学都知道,我们可以在:root中以--的方式显式声明自定义属性,并且在CSS的属性中通过var()函数调用已声明的自定义属性:

:root {
    --brand-primary: #b300cc;
    --brand-secondary: #ffde00;
}

body {
    background-color: var(--brand-primary);
    color: var(--brand-primary);
}

我们也可以使用JavaScript提供的API来操作CSS自定义属性,比如使用.setProperty()动态给自定义属性设置值:

const themeStyles = document.body.style

themeStyles.setProperty('--brand-primary', '#ffde00')
themeStyles.setProperty('--brand-secondary', '#b300cc')

对于自定义的媒体查询,我们一般使用@custom-media做为前缀来定义:

@custom-media = @custom-media <extension-name> [ <media-query-list> | true | false ] ;

比如:

@custom-media --small-viewport (max-width: 30em);

@media (--small-viewport) {
    body {
        color: #90f
    }
}

媒体查询逻辑操作符

如果你接触过 条件CSS 的话,我想你对逻辑操作符并不会感到陌生。因为在条件CSS中的 @supports还是我们今天聊的@media中,逻辑操作符是不可或缺的。

CSS媒体查询中的逻辑操作符主要有 andnotonly 以及 逗号(,,使用它们可以联合构造复杂的媒体查询,其中通过“逗号”(,)分隔多个媒体查询,将它们组合为一个规则。

另外,这几个逻辑操作符有点类似于JavaScript中的 与(&)或(||非(!。其中:

  • and:运算符用于将多个媒体查询规则组合成单条媒体查询,当每个查询规则都为真时则该条媒体查询为真,它还用于将媒体功能与媒体类型结合在一起。
  • not:运算符用于否定媒体查询,如果不满足这个条件则返回true,否则返回false。 如果出现在以逗号分隔的查询列表中,它将仅否定应用了该查询的特定查询。如果使用not运算符,则还必须指定媒体类型。
  • only:运算符仅在整个查询匹配时才用于应用样式,并且对于防止较早的浏览器应用所选样式很有用。 当不使用only时,旧版本的浏览器会将screen and (max-width: 500px)简单地解释为screen,忽略查询的其余部分,并将其样式应用于所有屏幕。 如果使用only运算符,则还必须指定媒体类型。
  • , (逗号):逗号用于将多个媒体查询合并为一个规则。 逗号分隔列表中的每个查询都与其他查询分开处理。 因此,如果列表中的任何查询为true,则整个media语句均返回true。 换句话说,列表的行为类似于逻辑或or运算符。

若使用了notonly操作符,必须明确指定一个媒体类型!

接下来,我们分别来看看每个操作符具体使用的细节。

and

and逻辑符主要是让你将多个媒体查询规则(多个媒体属性或媒体属性与媒体类型)合并在一起。一个基本的媒体查询规则,即一个媒体属性与默认指定的all媒体类型,就像下面这样子:

@media (min-width: 20em) { 
    :root { 
        --font-size: 100%; 
    } 
}

如果你只想在横屏时应用这个规则,你可以使用and逻辑符,加上orientation特性,比如:

@media (min-width: 20em) and (orientation: landscape) {
    :root {
        --font-size: 100%;
    }
}

上面的规则是查询仅在可视区域宽度不小于20em并在横屏的设备下有效。如果,你仅想在电视媒体上应用,那么可以继续使用and逻辑符来合并媒体类型:

@media tv and (min-width: 20em) and (orientation: landscape) {
    :root {
        --font-size: 100%;
    }
}

not

not逻辑符应用于整个媒体查询规则,在媒体查询规则为假时返回真。比如monochrome就用于彩色显示设备上或一个600px的屏幕应用于min-width: 700px属性查询上。在逗号媒体查询列表中not仅会否定它应用到它应用到的媒体查询上而不影响其它的媒体查询。not逻辑符仅能应用于整个媒体查询规则,而不能单独地应用于一个独立的媒体查询规则。例如,not在下面的媒体查询中最后被计算:

@media not all and (monochrome) {
    /* 样式规则 */
}

上面的规则等价于:

@media not (all and (monochrome)) {
    /* 样式规则 */
}

而不是:

@media (not all) and (monochrome) {
    /* 样式规则 */
}

再来看一个示例:

@media not screen and (color), print and (color) {
    /* 样式规则 */
}

等价于:

@media (not (screen and (color))), print and (color) {
    /* 样式规则 */
}

如查使用not操作符,还必须设置媒体类型(比如screenprint)。如果不这么做,媒体类型将是all,然后你的媒体查询将读取not all,因此它不会应用到任何地方。

or 和 逗号分隔符

or逻辑操作符相当于JavaScript逻辑运符中的或,即,当你的媒体查询规则中有多个的时候,只要有一个规则符合条件,其结果就是true。另外,在媒查询中,还有一个特殊规则,那就是逗号(,)分隔符,其效果等同于or逻辑操作符。当使用逗号分隔的媒体查询规则时,如果任何一个媒体查询规则返回的是真,那对应的样式规则就会生效。逗号分隔的列表中每个媒体查询规则都是独立的,一个媒体查询规则中的操作符并不会影响其它的媒体查询规则。也就是说,逗号分隔的媒体查询规则列表能够作用于不同的媒体属性、类型和状态。

例如,如果你想在最小宽度为760px或横屏的手持设备上应用一组样式,可以这样写:

@media (min-width: 760px), handheld and (orientation: landscape) {
    /* 样式规则 */
}

正如上面代码所示,如果一个800px宽度的屏幕设备,将返回真,逗号前一部分规则相当于@media all and (min-width: 760px)将会应用于该设备并且返回真,尽管我的屏幕媒体类型并不与第二部分(逗号后面一部分规则)的手持媒体类型相符合。同样的,如果我是一个500px宽的横屏手持设备,尽管第一部分因为宽度并不匹配,但第二部分仍匹配,因此整个媒体查询也会返回值。

only

only逻辑操作符有点特殊,它隐藏了旧浏览器的整个查询(防止老旧的浏览器不支持带媒体属性的查询而应用到给定的样式)。换句话说,旧的浏览器不理解only逻辑操作符,因此会忽略整个媒体查询规则。否则只会无效:

@media only all and (min-width: 320px) and (max-width: 480px) {
    /* 忽略老的浏览器样式规则 */
}

not逻辑操作符类似,only逻辑操作符对于使用媒体类型是不可选的。

有时您可能想创建一个取决于多个条件的媒体查询。 这就是逻辑运算符使用的场景:notand,和 only。 此外,您可以将多个媒体查询合并到一个逗号分隔的列表中。 这使您可以在不同情况下应用相同的样式。

在前面的示例中,我们已经看到and运算符用于将媒体类型与媒体功能分组。 and运算符还可以将多个媒体功能组合到单个媒体查询中。 同时,not运算符会否定媒体查询,从而基本上颠倒了它的正常含义。 唯一的运算符可防止较早的浏览器应用样式。

CSS媒体查询值范围

在CSS的媒体特性除了精确的值之外还有一些是值范围,比如前面示例出现的min-widthmax-width等。换句话说,我们可以使用min-*max-*作为前缀,以表示最小或最大约束(值约束),他们可以创建一个匹配的值范围,而不必声明特定的值。

在媒体特性中有很多媒体特性类似于width可以接受min-*max-*,比如heightaspect-ratioresolutioncolorcolor-indexmonochrome等。

这里我们用min-widthmax-width来举例。对于min-widthmax-width是用来表达“最小的宽度”和“最大的宽度”。为了更好的帮助大家理解minmax的实际含义,用下图来描述:

有了这些基础,我们来看看示例。

如果你想向最小宽度的20em的手持设备或屏幕应用样式表,你可以使用下面这样的媒体查询规则:

@media handheld and (min-width: 20em), screen and (min-width: 20em) {
    /* 样式规则 */
}

如果你想大宽度在20em36em之间的屏幕运用不同的样式规则,则可以使用下面这样的媒体查询规则:

@media screen and (min-width: 20em) and (max-width: 36em) {
    /* 样式规则 */
}

如果你想大最小宽度为375px和最小高度为812px的屏幕上运用相应的样式规则,则可以像下面这样写:

@media only screen and (min-width: 375px) and (min-height: 812px) {
    /* 样式规则 */
}

另外你要是想通过横屏或竖屏来区分,则可以像下面这样:

@media only screen and (min-width: 812px) and (orientation: landscape) {
    /* 横屏样式规则 */
}

@media only screen and (min-width: 375px) and (orientation: portrait) { 
    /* 竖屏样式规则 */
}

甚至还可以根据屏幕分辨率来写:

@media
    only screen and (min-device-pixel-ratio: 3),
    only screen and (min-resolution: 384dpi),
    only screen and (min-resolution: 3dppx) { 
        /* Retina屏幕下的样式规则 */
}

CSS Media Queries Level 4对这方面做了一些改进,在媒体特性中引入了<<=>>==这样的比较运算符。这些运算符和min-*以及max-*有着等同的功能。比如:

@media (max-width: 30em) { 
    /* ... */ 
}

用运算符可以这样写:

@media (width <= 30em) { 
    /* ... */ 
}

使用min-*max-*and有组合媒体查询也可以用比较运算符来描述:

@media (min-width: 30em) and (max-width: 50em) { 
    /* ... */ 
}

/*等同于*/
@media (30em <= width <= 50em ) { 
    /* ... */ 
}

媒体查询断点

媒体查询断点是响应式设计的重要组成部分。

媒体查询使用断点来设置站点调整设备宽度的点。因此,你可以控制网站的布局,并根据用户的设备进行改变。我们前面的示例中在媒体特性中就有媒体查询断点的影子,比如:

@media screen and (min-width: 320px) {
    
}

其中min-width的值320px就是断点。使用媒体查询时,对于断点选择总是令人感到困惑和烦恼,因为终端屏幕的分辨率太多了:

简单地来说,在选择媒体查询断点时虽没有一组通用的断点或最佳实践,但应该至少使用三个断点以获得最大的设备灵活性:

/* width < 480px */
@media only screen and (max-width: 480px) {...} 

/* 481px < width < 768px */
@media only screen and (min-width: 481px) and (max-width: 768px) {...} 

/* 769px < with < 1280px */
@media only screen and (min-width: 769px) and (max-width: 1280px) {...} 

/*  width > 1280px */
@media only screen and (min-width: 1280px) {...}

你也可以根据自己的需要设置任意想要的断点。

如何使用媒体查询

有了上面的基础,我们来看看如何使用CSS媒体查询,这里主要从三个方面来看CSS媒体查询的使用。

HTML中使用媒体查询

虽然前面的内容主要是围绕着CSS的媒体查询展开的,似乎和HTML没有过多的关系。事实上并非如此。CSS媒体查询其主要作用就是根据一定的媒体查询特性(条件)给Web应用不同的CSS规则。在HTML中,有些标签元素和CSS是有着紧密的联系,比如<link><style>标签。

<link rel="stylesheet" href="style.css" />

<style>
    .element {
        color: #fff;
    }
</style>

其中<link>标签通过href可以引入项目本地的.css文件,也可以引入外部CDN上的.css文件;<style>可以将CSS样式规则以内联的方式插入到对应的.html文件中。<link><style>以不同的方式将CSS样式引入到页面中,但它们有一个共同点,即 都有media属性media属性的内容有点类似于CSS的@media规则,它也可以包含媒体类型,媒体特性和媒体逻辑操作符等。

<link href="print.css" rel="stylesheet" media="print" />

<link href="mobile.css" rel="stylesheet" media="screen and (max-width: 600px)" />

<style media="print">
</style>

<style media="screen and (max-width: 600px)">
</style>

正如上面代码所示,<link><style>标签可以根据指定的media属性的值来做判断样式文件(或样式)加载。不过有一点需要明确的是,这并不总是阻止不匹配媒体查询的样式表下载,它只是给它们分配一个较低的加载优先级。因此,像智能手机这样的小屏幕设备访问Web应用时,它将只下载媒体查询中匹配其视口大小的样式表,比如说mobile.css。如果你要用打印机访问你的Web页面(打印Web页面),那么它只下载匹配打印机设备相关的样式,比如print.css

在HTML中除了<link><style>标签中可以使用media之外,在HTML5中的<picture>标签元素的子元素<source>也可以使用media属性。它可以使<picture>元素根据媒体特性来加载图像资源:

<picture> 
    <source media="(min-width: 900px)" srcset="cat-vertical.jpg"> 
    <source media="(min-width: 750px)" srcset="cat-horizontal.jpg"> 
    <img src="cat.jpg" alt="cat"> 
</picture>

CSS中使用媒体查询

前面大部分篇幅都是围绕着CSS中媒体查询而展开的,它们直接出现在CSS样式表中的@media规则中:

@media only screen and (min-device-width: 320px) and (max-device-width: 480px) {
    .element {
        background: #bada55;
    }
}

不过,除了上面这种姿势之外,在CSS样式表中使用媒体查询还有另一种方式。那就是在样式表中使用@import引入.css文件时,可以在其后面紧跟媒体查询,比如:

@import "print.css" print;

@import "animation.css" (prefers-reduced-motion: no-preference);
@import "retina-media.css" (prefers-reduced-data: no-preference);
@import "dark.css" (prefers-color-scheme: dark);

@import "mobile.css" (max-width: 320px);
@import "hd-colors.css" (dynamic-range: high);

JavaScript中使用媒体查询

可能大家最为熟悉的是在CSS中使用媒体查询,其实在JavaScript中也可以对媒体查询进行操作。我们可以通过windowmatchMedia()方法来对媒体查询进行操作。

matchMedia()方法是CSSOM中的一个API,它将返回MediaQueryList,而且返回的MediaQueryList可被用于判断Document是否匹配媒体查询,或者监控一个document来判定它匹配了或者停止匹配了此媒体查询。

其用法几乎与CSS媒体查询相同。我们将媒体查询字符串传递给matchMedia(),然后检查.matches属性:

const mediaQuery = window.matchMedia('(min-width: 320px)')

正如上面所示,我们定义的媒体查询将返回MediaQueryList对象:

它是一个存储有关媒体查询的信息的对象,我们需要的关键属性是.matches。这是一个只读布尔值,如果文档与媒体查询匹配,则返回true,否而返回的是false

只不过,.matches只适合一次性瞬时检查,但不能连续检查,并根据检测做出相应的更改。也就是说,如果希望随着视窗宽度改变了,.matches要得到相应的变化,那就需要其他的一些API的支持。好在MediaQueryList有一个addListener()removeListener()方法,它们都可以接受一个回调函数(.onchange事件),该函数在媒体查询状态更改时被调用。换句话说,当条件发生变化时,我们可以触发其他函数,从而允许我们响应更新后的条件。

const mediaQuery = window.matchMedia('(min-width: 768px)')

function handleTabletChange(e) {
    if (e.matches) {
        console.log('Media Query Matched!')
    }
}

mediaQuery.addListener(handleTabletChange)

handleTabletChange(mediaQuery)

为了考虑老的浏览器兼容方式,还可以使用最常见的方法,即监听windowresize,并且通过window.innerWidthwindow.innerHeight来做判断:

function checkMediaQuery() {
    if (window.innerWidth > 768) {
        console.log('匹配媒体查询')
    }
}

window.addEventListener('resize', checkMediaQuery);

我们来看一个简单的示例:

尝试着调整浏览器视窗大小,你可以看到下图这样的效果:

小结

写到这里,有关于CSS媒体查询相关的也就到了一个结束的节点。这篇文章向大家阐述了有关于CSS媒体查询的基本特性,使用规则等。对于CSS媒体查询而言,看上去并不复杂,但它所涉及到的知识点很多,而且要使用好CSS媒体查询还是有一定的难度。另外,CSS媒体查询是实现Web响应式设计不可或缺的部分,在后面我们会花一些时间来向大家阐述,如何基于CSS媒体查询来构建Web响应式设计。如果您对这部分内容感兴趣的话,请观注后续的相关更新。