下一代响应式Web设计:组件驱动式Web设计

发布于 大漠

2022年09月05日更新

2022年WOT全球创新技术大会分享的视频:

相关PPT可以点击这里下载

自从著名设计师 Ethan Marcotte(@beep)在 A List Apart 上发表了一篇名为《 Responsive Web Design 》的文章之后,响应式网页设计(RWD,即 Responsive Web Design)的身影就出现在了公众面前。自此就有了响应式 Web 设计这个概念。从提出这个概念到今天已经有十多年的时间了。在这十多年来,CSS 也发生了巨大的变化,新增了很多新的特性,近两年尤其如此(详细请参阅《2022 年的 CSS》一文)。这些变化,对于响应式Web设计的开发也有较大的改变。Una Kravets(@Una)大神,在2021的Google I/O 大会上的分享,提出 新的响应式:组件驱动式 Web 设计。 Web 生态即将进入响应式 Web 设计的新时代,并转变我们对其含义的看法,也为会Web设计带来新的变化。组件式驱动 Web 设计(或开发)也被称为是下一代响应式 Web 设计(或开发)。如果你对这方面话题感兴趣的话,请继续往下阅读。

响应式 Web 设计的发展历程

既然要聊响应式Web设计,那么我们就花一点篇幅和时间简单地了解一下其发展历程。

众所周知,自从 Tim Berners-Lee 创建第一个 Web页面(大约在1991年8月份左右)到90年代末,Web页面都是非常简陋的:

直到90年代末20年代初,Web设计和用户体验随着 CSS 的到来才慢慢地有了美感,Web页面看起来开始像我们今天使用的网站:

正如上图所示,越往后,Web 的 UI 越来越丰富,越来越漂亮。这也让 Web 开发人员不得不在布局、设计和排版等方面花费更多的时间。虽然 Web 开发人员为 Web 布局花费不少时间,但在这个过程中,也积累了很多不同的布局方法。在早期,Web 开发人员主要采用 固定宽度流式布局 两种布局方案来实现 Web 页面的布局。特别是流式布局,自 Glenn Davis 提出和推广之后,可谓是轰动一时,并且长期以来,都认为流式布局就是响应式Web布局。

流式布局(Liquid Layout)可以调整Web页面尺寸以适应不同的显示器分辨率或浏览器窗口的大小。

来看一个流式布局的简单示例:

但流式布局并不是完美的。使用流式布局的Web页面上,内容可能会溢出,在较小的屏幕上文字可能会换行,在较大的屏幕上可能会有很多不必的空白。

大多数流体布局在 800px x 600px1280px宽或更大的屏幕分辨率下看起来还不错。然而,如果我们能把它分割得更细一些,比如为 800px ~ 1024px1024px ~ 1280px以及1280px以上的分辨率提供不同的定制布局,那效果会更佳。同样,对于 640px ~ 800px320px ~ 640px240px ~ 320px 以及 240px 以下的分辨率也可以定制不同的布局。

大约在 2004 年的时候,Cameron Adams(@themaninblue) 在他的博文《Resolution dependent layout》提出了基于屏幕分辨率来动态构建Web布局,即 使用JavaScript根据浏览器窗口大小加载不同CSS文件

<!-- Narrow style sheet -->
<link rel="alternate stylesheet" type="text/css" href="css/narrow.css"
title="narrow"/>

<!-- Default style sheet -->
<link rel="stylesheet" type="text/css" href="css/normal.css" title="default"/>

<!-- Wide style sheet -->
<link rel="alternate stylesheet" type="text/css" href="css/wide.css" 
title="wide"/>

<!-- Included JavaScript to switch style sheets -->
<script src="scripts/dynamiclayout.js" type="text/javascript"></script>

注意,在所有 <link> 标签上title属性的值分别为 narrowdefaultwide,并且在 dynamiclayout.js 中有一个 DynamicLayout() 函数,将会根据 <link> 标签的title属性的值来调用不同的样式表:

function dynamicLayout(){
    var browserWidth = getBrowserWidth();

    // Narrow CSS rules
    if (browserWidth < 640){
        changeLayout("narrow");
    }
    // Normal (default) CSS rules
    if ((browserWidth >= 640) && (browserWidth <= 960)){
        changeLayout("default");
    }
    // Wide CSS rules
    if (browserWidth > 960){
        changeLayout("wide");
    }
}

@Kevin Hale 的博文《Dynamic Resolution Dependent Layouts》详细介绍该技术。

这种布局技术后来就以 Cameron Adams 博客文章标题命名,被称为 分辨率相关的布局。这种布局技术虽然会额外增加开发者(开发者需要为不同定制的布局提供不同样式表)工作量,但在CSS媒体查询流行之前还是很受欢迎的。它也被称为是早期的 CSS 媒体查询(通过JavaScript查询断点)。

虽然这种依赖动态分辩率布局的方案可以在不同的分辨率下提供更佳的体验,但随着 2005年08月10日 Opera 软件公司推出Opera Mini 和 2007年01月09是第一台 iPhone 手机的出现,市场上不同品牌,不同分辨率的移动端以及品牌商自己的Web浏览器就越来越多。

在这种环境之下,基于动态分辨率加载不同的样式表已不太现实,Web开发者不得不想出其他的方案来适应不同的屏幕尺寸。在很长一段时间,甚至到今日,为了适应不同屏幕的尺寸适配,为移动端单独创建一个网站,即 移动子域名网站。比如 Facebook的桌面版本和移动端版本,采用两个不同的域名来访问:

如此一来,开发人员要开发两个版本,相应的工作量就更大了,特别对于要快速响应和试错的Web应用来说,难度变得更大。

Web开发人员为了能改善这种现象,在 2010 年的时候,Ethan Marcotte(@beep) 基于 John Allsopp(@johnallsopp) 的 《网页设计的道(A Dao of Web Design)》,提出响应式Web设计思路(《Responsive Web Design》)。从此响应式Web设计(Responsive Web Design,简称 RWD)的身影就出现在了公众面前。

Ethan Marcotte(@beep) 在《Responsive Web Design》中提到,响应式这个词源自于建筑学领域,原本指的是建筑物本身会“响应”实际的使用情况,来自我调整。在Web开发领域,“响应式”的意思就变成了,我们开发的Web页面会“响应”用户的设备尺寸而自动调整布局。在这篇文章中提到过,我们可以基于 流体网格(Fluid Grids)灵活的图片(Flexible Images)媒体查询(Media Queries) 三种技术来构建一个响应式Web网站或Web应用。

另外,Ethan Marcotte(@beep) 构建了第一个具有响应式的Web网页,可以说是响应式Web设计经典案例之一(只可惜现在打不开了):

那通过这个示例,大家对响应式Web设计是什么样的有了一个初步的了解了。其实,Ethan Marcotte 在他的文章中就提到:

未来我们应该这样,随着访问网页的设备增加我们不会为每个设备单独设计,而只会做一份设计,把每个设备作为这份设计要照顾的一个方面

也就是说,每个设备上都会去追求最佳的用户体验,设计会自动适应各个设备。在过去的时代,设计师精确的知道自己的媒介材质,比如一张 A4 纸张,一个名片,或者一张海报。但是在我们这个多屏时代,Web 设计必须有这样的思维,我们要为“任意尺寸”而去设计。

自 Ethan Marcotte 提出响应式Web设计思路以及基于不同断点(CSS 媒体查询替代JavaScript查询设备屏幕分辨)实现不同终端设备屏幕分辨率布局已有十多年了。大家都说,每十年就会看到一个生态系统迅速的发展,其中 CSS 也不另外。尤其是这两年,CSS 新增了很多新的特性,比如大家最为期待的容器查询特性@container、级联分层@layer 以及 CSS作用域 @scope 等。

正如 Una Kravets (@Una) 在2021的Google I/O 大会上的分享所说:

随着用户偏好查询、容器查询以及其他设备类型查询的出现(CSS新特性),Web社区即将进入响应式 Web 设计的新时代,并改变我们对其含义的看法。

换句话说,下一代响应式Web设计即将到来,或者说已经来到了我们身边。

响应式 Web 设计的现状

在聊一下代响应式Web设计之前,我们就要对响应式Web设计的现状有所了解。我们分别从两种不同角色(Web设计师和Web开发者)的角度来看响应式 Web 设计的现状。

先从 Web 设计师的角色来聊。

时至今日,虽然已是移动终端的天下,但如果要给不同终端提供设计稿的话,Web 设计师还是会依旧为不同的设备终端提供不同的设计稿。比如,为不同的设备视窗尺寸(如手机,平板和桌面端)提供不同的设计稿:

我们来看下简化后的不同版本的设计线框图,如下所示:

在上图中,设计师为卡片(Card)提供了三种不同的 UI 效果。虽然卡片在不同设备视窗下有着不同的UI效果,但他们构成的元素是相同,都有卡片容器、卡片缩略图、卡片标题 和 卡片描述等:

作为Web设计师,你已经使用了多个版本的布局来展示同一个组件三种不同状态下的UI变化。可以说把足够多的信息传递给了Web开发者!到这一步,Web 设计师已给 Web 开发者提供了具有响应式的Web设计稿。

接下来,再从另一种角色(Web开发者)来看响应式Web设计的现状。对于 Web 开发者而言,要实现上图中三种状态下的卡片UI效果非常简单。借助 CSS 媒体查询特性,在不同断点下调整CSS样式规则即可:

/* Mobile First */
.card {
    display: flex;
    flex-wrap: wrap;
    gap: 10px;
}

/* Tablet */
@media (min-width: 700px) {
    .card {
        gap: 20px
    }
}

/* Laptop and Desktop */

@media (min-width: 1024px) {
    .card {
        position: relative
    }

    .card__thumb {
        position: absolute;
        inset: 0;
    }
}

这种方式只能适合于同一组件独立存在于不同版本下。就示例的设计稿来看,在桌面端有两种效果的卡片,为了满足该设计效果,我们需要额外添加一些类名,在不同状态下为卡片处理不同的UI效果:

CSS样式代码可能会是像下面这样:

/* Mobile First */
.card {
    display: flex;
    flex-wrap: wrap;
    gap: 10px;
}

/* Tablet */
@media (min-width: 700px) {
    .card--vertical {
        gap: 20px
    }
}

/* Laptop and Desktop */

@media (min-width: 1024px) {
    .card--featured {
        position: relative
    }

    .card--featured .card__thumb {
        position: absolute;
        inset: 0;
    }
}

看上去是不错,但问题是,只有当视窗宽度大于一个特定的值时(常指的分辨率断点值),相应的组件变体才会生效,比如当视窗宽度大于 700px时,.card--vertical卡片UI效果才生效;当视窗宽度大于1024px时,.card--featured卡片UI效果才生效。换句话说,如果要在平板端看到.card--featured卡片效果就无法看到,因为它的媒体查询在 1024px 或更大的视窗宽度下才会生效。

不仅如此,Web的内容是动态的,有的时候输出的内容可能和设计预定的卡片数量不相符合,那么在这种情况之下,要么会有一个空的空间,要么卡片会扩展以填补容器的剩余(或可用)空间。比如我们这个示例中,在视窗宽度为 700px 或更大的视窗宽度中,.card--vertical.card--featured 都有可能出现这样的场景。

针对这样的场景,Web设计师可能更希望有额外的UI效果给卡片。这部分我们放到下一节来聊。

简单地说,目前的响应式 Web 设计主要方案还是利用 CSS 媒体查询特性在不同的终端上提供布局的切换。虽然他能满足 Web 页面布局大部分场景,但也相对丧失了一些其他能力,比如说将响应式的样式注入到组件本身的能力。换句话说,基于视窗宽度查询构建的响应式Web页面,尤其是响应式组件,其能力是有限的。

CSS 媒体查询的不足

在上一节中我们一起探讨了 Web 开发者可以借助于 CSS 媒体查询特性来查询视窗宽度(或设备其他特性)为 Web 页面在不同终端提供差异化的布局。但也暴露出很多不足之处,甚至是明显的能力不足。就拿前面示例来说,卡片组件有三种差异化的UI效果,这些差异化的UI效果是取决于浏览器视窗宽度,也就意味着卡片组件不能根据其父容器宽度去调整 UI 风格。这就限制了开发者 只能在视窗宽度大于某个特定值时(断点) 使用一个组件的特定样式。例如视窗宽度到达 700px 或大于700px时,卡片组件从默认的.card(水平)状态切换到垂直(.card--vertical)状态。也就是说,如果我们想在小于700px宽度的视窗下,使用垂直状态(.card--vertical)卡片效果是不行的:

另外一点就是当服务端吐出的数据和设计师预设的数量不一致时,最终的Web效果有可能不是设计师期望的。比如只有一张卡片、两张、三张或更多,而设计稿中包含三张,这种情况之下,Web开发的实现的效果可能会像下图这样:

正如上图所示,如果只有一张卡片数据吐出的时候,整个卡片宽度会扩展与容器宽度相等(拉伸)。此时,卡片宽度太宽导致用于卡片上的缩略图被拉抻,有可能会使缩略图变得模糊。

事实上呢?这只是Web开发者的一厢情愿,设计师真正的意图可能是像下图这样:

在这种场景之下,使用 CSS 媒体查询特性实现起来会比较棘手,但使用 CSS 容器查询特性,就会容易地多,我们可以通过查询卡片父容器来决定如何显示卡片去解决这些问题。

如果用一然话来概述的话:

虽然 Web 开发者可以使用全局的视窗信息来设置组件的样式,但组件仍然不能拥有自已的样式,而且当我们的设计系统基于组件而不是基于页面时,那么媒体查询就无能为力!

庆幸的是,生态系统正在改变,而且这两年尤其突出。比如说,我们一直期望的容器查询特性就如约而至。如果我们将组件自身的响应式样式思路从查询视窗转换到查询其祖先容器,是不是前面的问题就可以轻易解决。除此之外,早期的CSS媒体查询只能查询视窗宽度和媒体特性,但不能查询用户对设备喜好的设置。不过,这两年CSS媒体查询特性也有巨大的变化,我们除了查询媒介(设备)特性之外,也可以查询用户偏好设置。

正如 Una Kravets 所言:

Web生态再一次让CSS腾飞,这些新的特性将会让响应式Web设计注入新的能力。我们也将进入响应式设计的新时代,并转变我们对其含义的看法。

下一代响应式 Web 设计

当媒体查询被运用于 CSS 中时,“响应式Web设计”(Responsive Web Design)一词就被 Ethan Marcotte 在 2010年创造出来。从那时起,Web设计师和开发人员就开始使用响应式Web设计的方法来设计和开发一个沉浸式的Web页面或Web应用,以实时适配当今看上去无底洞的终端设备。

今天,当我们提到响应式Web设计时,首先想到的是Ethan Marcotte(@beep) 的 《Responsive Web Design》博文中提到过的基于 流体网格(Fluid Grids)灵活的图片(Flexible Images)媒体查询(Media Queries) 三种技术来构建一个适应不同屏幕尺寸或不同移动终端设备的 Web 页面。

Web 开发者使用 CSS 媒体查询来改变整个页面的布局,并从上到下调整移动手机、平板电脑和桌面端的设计尺寸。这种方法很有效,而且效果很好,但它有一个明显的缺陷,即 整个屏幕同时响应,或者说整个页面同时响应。虽然该响应方式确实对用户的体验有一些较大的影响,但不能响应个别用户的需求,也缺乏将响应式注入组件本身。

好消息是,生态系统正在改变,而且进步非常迅速。Web 设计师和Web平台的工程师正在开始用一种新的响应式技术方案来构建Web页面,这种方案被称为组件驱动式Web设计(Component-Driven Web Design)

组件驱动式Web设计

我们今天使用的响应式 Web 设计方法很快就会被认为是过时的,就像我们从90年代最初的基于表格的HTML开发过渡到现在的感觉一样。

Web 设计师现在要克服的挑战是,目前的响应式 Web 设计方法本质上是一种一刀切的方法,把整个页面和用户体验当作一个整体,没有任何个性化。

基于视窗的媒体查询(CSS 媒体查询)给我们提供了许多媒体查询的能力,但缺少为我们的Web设计提供精细度的能力,并创造一个对用户、他们的环境和他们在页面上采取的行动来说是独特的体验。我们也缺乏将响应式样式注入组件本身的能力。

这里所说的组件是Web上的元素,可以由其他设计元素的集合或分组组成。如果我们把组件看成是由积木组成的,并把这个概念应用到像幻灯片、卡片或内容块这样的常见UI元素的构造中,就会更容易理解,在不久的某一天,我们可能会把响应式设计样式注入单个组件或积木中,以定制和调整用户的体验,而不是把一套固定的样式和规则应用于整个页面的布局。

我们可以使用全局视窗信息来控制元素的字体大小和最大宽度等样式,或者调整这些组件的背景图像和布局,但它们仍然无法控制拥有自己的样式。当我们的响应式设计系统是基于组件的,而不是基于页面的时候,这种限制就更难了。

好消息是,全世界的Web设计师和开发人员正在努力改变响应式Web设计的生态系统。尽管为了改变我们对响应式设计的思考方式以及组件如何适应其周围环境,需要进行基本的设计思维过程的改变,但响应式设计专业人员处理响应式Web设计的方式正在快速变化。

现在为创新之火推波助澜的是CSS和灵活布局的快速发展,比如添加了一些新的查询规则、Flexbox 和 Grid布局。这里所取得的进步正在迅速迎来一个新的响应式Web设计的时代,而这个时代就在地平线之外。

CSS生态快速的发展,即将彻底改变响应式 Web 设计的概念

现在,在我们被引入响应式 Web 设计这个激进的新概念的十多年后,我们又一次见证了响应式设计生态系统的演变,即 CSS新增的特性将直接基于组件而不是基于页面注入样式响应能力。这种能力被称为 组件驱动Web设计(Component-Driven Web Design),基于组件驱动的开发将会成为一种真正流行的开发模式。

为了理解这种开发模式的转变,并为即将到来的变化浪潮做好准备,让我们看看在响应式Web设计运动中我们可以期待的变化,以及这可能会如何改变我们对待响应式设计的概念。

响应用户的需求

你可能对基于视窗可视区域大小的媒体查询(通过 min-widthmax-widthmin-heightmax-heightorientationaspect-ratio 等)比较熟悉,比如:

@media (max-width: 45rem) {
    /* 视窗小于 45rem */
}

@media (min-width: 45rem) {
    /* 视窗大于 45rem */
}

这只是 CSS 的 @media 最基础的一部分规则,事实上,@media 规则大约包含了 24 个可供查询的特性,其中大约 19 个查询规则得到较好的支持,详细的可以阅读《图解CSS: CSS媒体查询》一文。在这些新增的查询特性中是用来改善用户体验的,比如 Media Queries Level 5 规范中的第十一部分,能够让你根据用户自身的特定偏好和需求来设计 Web 体验。

也意味着这些新增的媒体查询特性允许你根据用户的偏好来调整用户的体验。

现在很多设备提供了一些用户偏好的设置。比如在 Mac 电脑上,用户可以根据自己喜好做一些设置:

CSS媒体查询提供了一些用户喜好的查询特性,这些特性可以识别出用户在系统上的偏好设置,帮助Web开发者构建更加健壮和个性化的 Web 体验,特别是对于那些具有可访问性需求的用户。

prefers-reduced-motion

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

使用prefers-reduced-motion媒体查询用于检测用户的系统是否被开启了动画减弱功能。比如下面的这个示例,将会展示一组令人心烦的动画,不过当你开启了系统的“减少运动”后就能看到动画减弱的效果了。

.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;
    }
}

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

另外,Eric Bailey 在他的文章《Revisiting prefers-reduced-motion, the reduced motion media query》中提出了一个观点:

“并不是每一个可以访问网络的设备都可以呈现动画,或者流畅地呈现动画。”

对于刷新率低的设备来说,可能会导致动画出现问题,比如动画卡顿。这样的话,删除动画可能是更好的选择。我们可以将 prefers-reduced-motionupdate 结合在一起使用:

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

这段代码强制所有使用animation-durationtransition-duration声明的动画以人眼难以察觉的速度结束。当一个人要求减少动效体验,或者设备有一个刷新率较低的屏幕,比如电子墨水或廉价的智能手机,它就能发挥作用。

但需要注意的是,使用动态减弱并不意味着“没有动效”,因为动效在Web页面中传达信息能起到至关重要的作用。相反,你应该使用一个坚实的、去除非必须的动效基础体验去引导这些用户,同时逐步增强没有此项偏好设置的其他用户的体验。

如果你对减弱动效效果这方面技术感兴趣的话,还可以阅读:

prefers-color-scheme

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

使用 prefers-color-scheme 查询特性可以让你对用户是否打开了设备上Dark Mode来做出响应。换句话说,给Web页面或应用添加Dark Mode只需要几行代码即可。首先我们默认加载的主题是亮色系,我们可以在 :root 中声明亮色系所需要的颜色,比如:

:root {
    --text-color: #444;
    --background-color: #f4f4f4;
}

然后通过媒体查询prefers-color-scheme: dark为暗色系重置所需要的颜色:

@media screen and (prefers-color-scheme: dark) {
    :root {
        --text-color: rgba(255,255,255,.8);
        --background-color: #121212;
    }
}

使用prefers-color-scheme 来定制不同外观主题时,还可以和theme-color 以及 color-scheme 结合起来使用。这将能控制系统应用的(比如浏览器)主题颜色:

color-scheme 这个 CSS 属性和<meta>nametheme-color是相同的。它们都是让开发者更容易根据用户的喜好设置来控制 Web应用或页面的主题,即 允许开发者根据用户喜好设置添加特定的主题样式。其实 color-scheme 属性和 相应的<meta>标签与prefers-color-scheme相互作用,它们在一起可以发挥更好的作用。最重要的一点是, color-scheme完全决定了默认的外观,而prefers-color-scheme则决定了可样式化的外观

假设你有下面这样的一个简单页面:

<head>
    <meta name="color-scheme" content="dark light">
    <style>
        fieldset {
            background-color: gainsboro;
        }
        @media (prefers-color-scheme: dark) {
            fieldset {
                background-color: darkslategray;
            }
        }
    </style>
    </head>
    <body>
        <p>
            Lorem ipsum dolor sit amet, legere ancillae ne vis.
        </p>
        <form>
            <fieldset>
                <legend>Lorem ipsum</legend>
                <button type="button">Lorem ipsum</button>
            </fieldset>
        </form>
</body>

页面上<style>中的CSS代码,把<fieldset>元素的背景颜色设置为gainsboro,如果用户更喜欢暗色模式,则根据prefers-color-scheme媒体查询,将<fieldset>的背景颜色设置为darkslategray

通过<meta name="color-scheme" content="dark light"> 元数据的设置,页面告诉浏览器,它支持深色(dark)和亮色(light)主题,并且优先选择深色主题。

根据操作系统是设置为深色还是亮色模式,整个页面在深色上显示为浅色,反之亦然,基于用户代理样式表。开发者没有额外提供 CSS 来改变段落文本或页面的背景颜色。

请注意,<fieldset> 元素的背景颜色是如何根据是否启用了深色模式而改变的,它遵循了开发者在页面上提供的内联样式表的规则。它要么是gainsboro,要么是darkslategray

上图是亮色模式(light)下,由开发者和用户代理指定的样式。根据用户代理的样式表,文本是黑色的,背景是白色的。<fieldset>元素的背景颜色是gainsboro,由开发者在内联的式表中指定的颜色。

上图是暗色模式(dark)下,由开发者和用户代理指定的样式。根据用户代理的样式表,文本是白色的,背景是黑色的。<fieldset>元素的背景色是darkslategray,由开发者在内联样式表中指定的颜色。

按钮<button>元素的外观是由用户代理样式表控制的。它的颜色被设置为ButtonText系统颜色,其背景颜色和边框颜色被设置为ButtonFace系统颜色。

现在注意<button>元素的边框颜色是如何变化的。border-top-colorborder-bottom-color的计算值从rgba(0,0,0,.847)(偏黑)切换到rgba(255, 255, 255, .847)(偏白),因为用户代理根据颜色方案动态地更新ButtonFace。同样适用于<button>元素的color属性,它被设置为相应的系统颜色ButtonText

看上去不错,但这也引出另一个新的概念,系统颜色

有关于系统颜色这方面的不在这里详细阐述,如果你感兴趣的话可以阅读:

prefers-reduced-data

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

你可能有过出差旅行的经历,也可能碰到了手机数据不够用,那么访问一个重图片的网站是很糟糕的(虽然说现在流量对于大家来说不是很大的事情,花钱总是能摆平的)。不过,一旦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上的应用程序减少网络数据的使用:

插个题外话,上面提到的这三个媒体查询特性主要是运用于 CSS 中,但它们还可以和 HTML 的 <picture> 元素的 <source> 标签元素结合起来使用。可以根据用户对设备的偏好设置来选择不同的图片源:

<!-- 根据 prefers-color-scheme 为不同模式选择不同图片 -->
<picture>
    <source srcset="dark.png" media="(prefers-color-scheme: dark)">
    <source srcset="light.png" media="(prefers-color-scheme: light)">
    <img src="light.png" alt="" />
</picture>

<!-- 根据 prefers-reduced-motion 为用户呈现动图或静态图 -->
<picture>
    <source srcset="animation.jpg" media="(prefers-reduced-motion: reduce)">
    </source>
    <img srcset="animation.gif" alt="" />
</picture>

<!-- 根据 prefers-reduced-data 为用户选择不同的图片 -->
<picture>
    <source srcset="light.jpg" media="(prefers-reduced-data: reduce)" />
    <img src="heavy.jpg" alt="" srcset="heavy@2x.jpg 2x" />
</picture>

prefers-contrast

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

比如像下面这个示例:

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

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

其他与用户偏好有关的媒体特性

从W3C规范中不难发现,规范中提供了六个有关于用户偏好的媒体查询特性:

媒体特性名称 媒体特性值 是否接受minmax 描述
prefers-reduced-transparency no-preference, reduce 用户是否倾向于选择更低的透明度
forced-colors active, none 检测是用户代理否限制调色板
light-level dim, normal, washed 环境光亮度

我们来看一个 forced-colors 的示例,该示例来自于 Eric 的 《Windows High Contrast Mode, Forced Colors Mode And CSS Custom Properties》一文:

提取使用 forced-colors 的示例代码:

// SCSS
.c-dialog__content {
    background-color: var(--dialog-color-background); 
    box-shadow: 0 1rem 2rem 0 #00000099;
    outline: var(--dialog-border-width) solid var(--dialog-border-color);
    padding: var(--dialog-padding-outer);
    width: min(90vw, 38rem);
    z-index: 1;
    
    @media (forced-colors: active) {
        --dialog-border-width: var(--size-300); 
    }
}

forced-colors媒体查询特性中重新定义了 --dialog-border-width 的值。这样做的原因是一个非常有意思的调整。它把细的焦点框(outline)变成了一个粗的。这样调整有助于显示模态框的外部边界,并传达它是漂浮在页面其他内容之上的信息。强制色彩模式删除了模态框的盒子阴影(box-shadow),所以我们不能在这种专门的浏览模式下依赖这种视觉效果:

如果你对上面提到的媒体查询特性感兴趣的话,可以深入阅读下面这几篇文章:

响应容器的需求

CSS 的 媒体查询 引发了一场响应式设计的革命,为开发者提供了一种方法来查询用户代理或设备环境的各个方面,比如视窗的大小或用户偏好来改变 Web 页面的风格。直到现在,媒体查询还做不到让元素的样式能根据一个最近的容器的大小来改变样式风格。也正因此,大家一直期待的容器查询来了。

很难想象,从基于页面的响应式设计(媒体查询)到基于容器的响应式设计(容器查询)的转变,对设计生态系统的发展会起到什么作用?

为了回答这个问题,接下来我们分几个方面来介绍,希望能给大家带来一定的思考,从而得到自己想要的答案。

容器查询的发展历程

正如我在《2022年的CSS》一文中所提到的那样。一直以来,CSS 容器查询都是大家期待的一个特性,在这几年的CSS发展报告中,他一直位居第一:

自 2010 年 @Ethan Marcotte 首次提出 响应式Web设计(RWD) 的设计理念(概念),Web设计就进入现代Web布局时代。开发者可以根据 CSS 媒体查询特性 (通常是视窗宽度、媒体设备特性等)来为Web页面定制不同的表现形式,比如可以根据用户浏览内容的设备特性来呈现不同的布局、不同的字体大小和不同的图片等。

但对于 Web 设计师或Web开发者来说,在现代Web设计或布局中仍然缺少一特性,页面的设计不能够响应其容器的宽度(或其他特性)。也就是说,如果Web开发者能够根据容器宽度来改变UI样式,那就更好了。容器查询将在很大程度上帮助 Web 开发者更好的完成他们的工作,在为Web开发基于组件代码时,其遗漏(容器查询特性的缺失)是一个巨大的限制。

正因此,有关于容器查询的特性在社区中的探讨就没有停止过。

早在 2019 年底,@Zach Leatherman 在寻找容器查询起源时,找到的最早有关于容器查询的解决方案是 @Andy Hume基于 JavaScript 的选择器查询和响应式容器的解决方案

2015 年, @Mat ‘Wilto’ Marquis 在响应式图片社区小组引入了 <picture> 元素,将响应式图片带到了响应式 Web 设计的世界,他在《Container Queries: Once More Unto the Breach》一文中概述了元素查询的挑战和使用案例演示了容器查询的特性。

然后,在2017年,@Ethan Marcotte写了一篇关于容器查询相关的文章,并提出了这样的看法:

在他最初关注的响应式 Web 设计的文章之后的几年里,Web设计师和开发人员的工作越来越集中在组件上,而不是整个页面,这使得媒体查询不那么理想。

从那时起,虽然有很多人主张使用媒体查询,但容器查询向前推进的速度还是不够理想。@L. David Baron在《Thoughts on an implementable path forward for Container Queries》中简明扼要地解释了容器查询向前推进慢的问题出在哪?

容器查询要求样式取决于组件的大小,但考虑到 CSS 的工作原理,组件中的样式会影响其大小。任意打破这个循环,既会产生奇怪的结果,又会干扰浏览器的工作,还会增加浏览器优化的成本。

除了 @David Baron 之外,2018年6月,@Greg Whitworth在荷兰阿姆斯特丹举办的 CSS Day + UX Special 活动上的主题分享《Over the moon for container queries》中也解释了容器查询在Web平台上推进慢的相关原因。更重要的是,@Greg Whitworth还提供了使用新的 JavaScript API 和 CSS 的新技术来实现容器查询的特性。@David Barrrron 也提出了一个可以避免这种困境的策略,更重要的是 @Miriam Suzanne 在 @David Baron 的策略基础上提出了 @container 方法

@container 方法通过对被查询的元素应用大小和布局的限制来实现。任何具有尺寸和布局限制的元素都可以通过一个新的 @container规则进行查询,其语法与现有的媒体查询类似。

这个提议已经被 W3C 的 CSS 工作组采纳,并已经添加到 CSS Containment Module Level 3 模块中。有关于该功能的相关问题和各网格平台推进进度,可以点击这里查阅

CSS Containment Module Level 3 还是 FPWD 版本,规范中所描述的语法不是最终版本,直到写这篇文章,其语法规则还在变,因此文章中所展示的语法有可能会变以及相关的示例有一天就无效了:

什么是容器查询

CSS 容器查询最大的特点是:

容器查询允许开发者定义任何一个元素为包含上下文,查询容器的后代元素可以根据查询容器的大小或计算样式的变化来改变风格!

换句话说,一个查询容器是通过使用容器类型属性(container-typecontainer)指定要能的查询类型来建立的。适用于其后代的样式规则可以通过使用@container条件组规则对其进行查询来设定条件。

容器查询为响应式设计提供了一种更加动态的方法。这意味着,如果你将此卡片组件放在侧边栏或放在页面主体内部的网格中,则该组件本身根据容器而不是视口进行响应式的信息展示。

首先,把卡片放到一个容器元素中,比如.card__container

<!-- HTML -->
<div class="card__container">
    <div class="card">
        <img src="https://picsum.photos/2568/600?random=1" width="2568" height="600" alt="" class="card__thumbnail" />
        <div class="card__badge">Must Try</div>
        <h3 class="card__title">Best Brownies in Town</h3>
        <p class="card__describe">High quality ingredients and best in-class chef. Light, tender, and easy to make~</p>
        <button class="card__button">Order now</button>
    </div>
</div>

也就是说,当卡片组件被放在一个容器中时,代表着它被包含在该容器中,比如上面代码中的.card__container。这也意味着,我们可以使用 CSS 的 container来查询.card__container 的宽度,并在@container.card 设置不同的样式规则。从而达到设计师真正的意图:

比如,容器宽度(.card__container)分别在 >400px>550px>700px 时为.card设置不同样式:

代码可能像下面这样:

/* Default */
.card {
    // ...
}

/* CSS Container Queries*/
.card__container {
    container-type: inline-size;
}

/* container's width > 400px*/
@container size(width > 400px) {
    .card {
        // ...
    }
}

/* container's width > 550px*/
@container size(width > 550px) {
    .card {
        // ...
    }
}

/* container's width > 700px*/
@container size(width > 700px) {
    .card {
        // ...
    }
}

拖动卡片右下角的滑块,改变 .card__container 容器大小,你可以看到卡片组件(.card)UI效果的变化:

@container规则,其工作方式与使用@media的媒体查询类似,但相反,@container查询父容器以获取信息,而不是视口和浏览器的UserAgent

容器查询的使用

到目前为止,CSS 容器查询的语法规则已经经历了多个版本更新,上面示例中展示是最新的使用方式。下面这几篇文章中可以索引到其每个版本的使用方式的差异:

接下来,通一个容器查询卡片的示例来向大家展示如何使用 CSS 容器查询。

定义一个包含性上下文

要使用 CSS 容器查询特性,首先要定义一个包含性上下文(Containment Context)。这个有点类似于使用 Flexbox 和 Grid 布局(定义Flexbox 或 Grid 上下文使用的是 display 属性),只不过,定义一个包含性的上下文使用的不是我们熟知的 display 属性,而是一个新的CSS属性,即 container

在一个元素上显式使用 container 可以告诉浏览器以后要针对这个容器进行查询,以及具体如何查询该特定的容器。比如,上面演示的示例中,我们在 .card__container 元素上(.card的父容器)显式设置了 container-type 的值为 inline-size:

.card__container {
    container-type: inline-size
}

上面的代码告诉浏览器,可以基于.card__container容器的内联轴(Inline Axis)方向尺寸变化进行查询。也就是说,当.card__container容器宽度大小变化到指定的某个值时,其后代元素的样式就可以进行调整。

container-typecontainer 属性中的一个子属性,另外,还可以显式使用 container-name 来命名你的容器,即给一个包含性上下文指定一个具体的名称:

.card__container {
    container-name: card
}

这种方式对于同一个上下文中有多个包含性上下文时非常有意义,可以更明确地知道哪些查询会影响元素。

你可以使用简写属性container,只不过需要在 container-typecontainer-name 之间添加斜杠分割符/

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

/* 等同于 */
.card__container {
    container: inline-size / card;
}

如果一个容器查询被应用到一个没有定义的包含祖先元素上,查询将无法应用。也就是说,无论是 body 还是 html 元素,都没有默认的回退包含上下文。另外,定义包含上下文名称时不能是 CSS 的关键词,比如 defaultinheritinitial 等。

注意:container-name 可以省略,如果省略将会使用其初始值none,但 container-type 不可省略,如果省略的话则表示未显式声明包含性上下文!

定义一个容器查询

现在我们知道使用 container(或其子属性 container-typecontainer-name)对一个元素显式声明包含上下文(对一个元素应用包含性)。

有了这个包含性上下文之后,就可以使用 CSS 的 @ 规则@container来对应用了包含性元素进行查询,即对容器进行查询。@container 规则的使用和 @media 以及 @supports相似:

@container containerName size(width > 45rem) {
    /* 应用了包含性上下文后代元素的 CSS */
}

@container size(width > 45rem) {
    /* 应用了包含性上下文后代元素的 CSS */
}

这两种方式都是正确的使用姿势,第一个示例中的 containerName 指的是 container-name 显式声明的包含性上下文的名称。如果在@container 中没有指定查询的容器名称,那么这个查询将是针对离样式变化最近的声明了包含性上下文的元素进行查询。比如:

@container size(width > 30em) {
    .card {
        border-radius: 20px;
    }
}

表示这个查询将是针对 .card 元素最近的显式声明了包含性上下文的元素进行查询。

代码中的size() 函数是容器查询中的新语法规则。这也是容器查询语法变化之一,即 对查询类型进行了更明确的规定。 因为规范已经提高到不仅可以根据尺寸(size)属性查询,还可以根据样式(style)属性进行查询。

正如 Terrible Mia(容器查询规范设计者)在 Twitter 上分享的一样,可以使用 style() 函数对样式进行查询:

@container style(--card: large) {
    /* CSS Style */
}

@container size(width > 30em) and style(--card: large) {
    /* CSS Style */
}

到写这篇文章的时候,还没有浏览器支持对样式进行查询。另外,示例中用于 @container 的查询条件(width > 30em) 相当于 (min-width: 30em)。使用数学表达式要比使用 min-widthmax-width更易于理解,自 Media Queries Level 4 开始, 在 @media 规则中,也可以使用我们熟悉的数学表达式,比如>=<=等来替代以往不易于理解的min-max-

上面示例代码中同时出现 container@container,但他们并不是指的同一个属性,前者是一个CSS属性,后者是一个CSS代码块。而且两者有本质的区别:

  • containercontainer-typecontainer-name 的简写属性,用来显式声明某个元素是一个查询容器,并且定义查询容器的类型(可以由container-type指定)和查询容器的名称(由container-name指定)。
  • @container(带有@规则),它类似于条件CSS中的@media@supports规则,是一个条件组规则,其条件是一个容器查询,它是大小(size)和(或)样式(style)查询的布尔组合。只有当其条件为真(true),@container规则块中的样式都会被用户代理运用,否则将被视为无效,被用户代理忽略。
容器查询卡片

我想大家对容器查询的理论和概念有了一个初步的认识。接下来,我们把这些东西放到一起,来具体看看前面展示的容器卡片示例是如何实现的。

自从响应式 Web 设计的出现以及移动终端设备越来越多,在设计中也有移动端优先(Mobile First)还是桌面端优先(Desktop First)的争执:

如果你对这方面讨论感兴趣,可以阅读 Ahmad Shadeed 的 《The State Of Mobile First and Desktop First》一文。

就我个人而言,到目前为止,在开发跨组件状态的“断点”时,将容器查询与考虑“移动端优先”的设计是最有意义的。也就是说,将最窄的视图作为默认样式,然后通过容器查询处理更大宽度的样式更新。

如上图所示,我们从左往右来实现卡片不同状态断点下的UI效果。先从最窄的卡片开始(最左侧,Default 状态)。构建这个卡片组件,所需要的 HTML 结构如下:

<div class="card__container">
    <div class="card">
        <img src="https://picsum.photos/2568/600?random=1" width="2568" height="600" alt="" class="card__thumbnail" />
        <h3 class="card__title">Container Queries Rule</h3>
        <p class="card__describe">Lorem ipsum dolor, sit amet consectetur adipisicing elit. Quis magni eveniet natus nulla distinctio eaque?</p>
        <button class="card__button">Order now</button>
    </div>
</div>

我们通过 CSS Grid 来完成卡片的布局。先从最窄的开始,添加下面CSS代码:

.card {
    display: grid;
    gap: 1rem;
    margin: 5vh auto;
    border-radius: 0.5rem;
    box-shadow: 0 0.25rem 0.5rem -0.15rem hsla(0 0% 0% / 55%);
    background-color: #fff;
}

.card__thumbnail {
    max-width: 100%;
    aspect-ratio: 16 / 9;
    height: auto;
    object-fit: cover;
    border-radius: 0.5rem 0.5rem 0 0;
}

.card__title {
    font-weight: 700;
    font-size: clamp(1.2rem, 1.2rem + 3vw, 1.5rem);
    padding: 0 20px;
    white-space: nowrap;
    text-overflow: ellipsis;
    overflow: hidden;
}

.card__describe {
    color: #666;
    line-height: 1.4;
    padding: 0 20px;
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 3;
    overflow: hidden;
}

.card__button {
    display: inline-flex;
    justify-content: center;
    align-items: center;
    border: none;
    border-radius: 10rem;
    background-color: #feca53;
    padding: 10px 20px;
    color: #000;
    text-decoration: none;
    box-shadow: 0 3px 8px rgb(0 0 0 / 7%);
    transition: all 0.2s linear;
    font-weight: 700;
    justify-self: end;
    margin: 0 20px 20px 0;
    cursor: pointer;
}

.card__button:hover {
    background-color: #ff9800;
}

正如上面的效果所示,卡片组件可以随着其容器(.card__container)宽度自动变化,在窄屏下效果看上去还不错,但在宽屏下,效果看上去有点怪怪的。不过不用担心,这仅是最初的效果。我们期望的是通过容器查询的特性,在容器不同断点下改变卡片组件的布局。按照前面所介绍的,我们需要先创建一个包含性上下文,即在 .card__container 上使用 container 显式声明该元素是一个包容性上下文。

.card__container {
    container: inline-size;
}

然后我们就可以在不同断点下使用 CSS Grid 布局相关特性来调整布局,也就是添加不同的容器查询来更新卡片组件布局和样式。

/* 查询容器 .card__container 宽度大于或等于 400px */
@container size(width >= 400px) {
    .card {
        grid-template-columns: 180px 1fr;
        grid-template-areas:
        "thumbnail title"
        "thumbnail describe"
        "thumbnail button";
        gap: 10px 20px;
        align-items: start;
    }

    .card__thumbnail {
        grid-area: thumbnail;
        aspect-ratio: 1;
        border-radius: 0.5rem 0 0 0.5rem;
    }

    .card__title {
        grid-area: title;
        padding-left: 0;
        margin-top: 16px;
    }

    .card__describe {
        grid-area: describe;
        padding-left: 0;
        -webkit-line-clamp: 2;
    }

    .card__button {
        grid-area: button;
    }
}

/* 查询容器 .card__container 宽度大于或等于 550px */
@container size(width >= 550px) {
    .card {
        grid-template-columns: 240px 1fr;
        grid-template-rows: 56px auto 60px;
    }

    .card__describe {
        -webkit-line-clamp: 3;
    }
}

/* 查询容器 .card__container 宽度大于或等于 700px */
@container size(width >= 700px) {
    .card {
        grid-template-areas:
        "thumbnail"
        "title"
        "describe";
        grid-template-columns: auto;
        grid-template-rows: auto auto auto;
        gap: 1rem;
    }

    .card::before {
        content: "";
        background-color: rgb(0 0 0 / 0.4);
        background-image: linear-gradient(
        to top,
        rgb(0 0 0 / 0.8),
        rgb(0 0 0 / 0) 60%,
        rgb(0 0 0 /0.8) 100%
        );
        display: block;
        grid-row: 1 / -1;
        grid-column: 1 / -1;
        z-index: 2;
        min-height: 100%;
        aspect-ratio: 4 / 3;
        border-radius: 0.5rem;
    }

    .card__thumbnail {
        grid-row: 1 / -1;
        aspect-ratio: 4 / 3;
        border-radius: 0.5rem;
        z-index: 1;
    }

    .card__title {
        padding: 0 20px;
        z-index: 3;
        color: #fff;
    }

    .card__describe {
        -webkit-line-clamp: 2;
        padding: 0 20px;
        margin-bottom: 20px;
        z-index: 3;
        color: #ddd;
    }

    .card__button {
        display: none;
    }
}

效果如下:

可以在上面示例中尝试拖动卡片右下角滑块改变卡片容器宽度,你将看到的效果如下:

有了这样一个卡片组件之后,如果将其放在不同的位置,即使是同一页面,同一视窗断点下,也会根据其容器断点自动匹配最为适合的布局(或UI效果)。比如:

尝试调整上面示例中视窗的大小:

媒体查询 vs. 容器查询

通过上面的示例的介绍,我想你对容器查询特性已经有了一个较清晰的认识了。从使用角度来看,容器查询和媒体查询是非常的相似,那么有人可能会问,有了容器查询是不是就不再需要媒体查询特性了呢?在回答这个问题之前,我们简单的来看两者的差异。

众所周知,媒体查询查询的浏览器视窗宽度(当然还有其他查询特性),而容器查询查询的是组件其父容器(具有包含性上下文的祖先元素)的宽度(或样式)。下图可能可以清晰的阐述两者的差异:

就我个人认为,两者不是谁替代谁的关系,更应该是两者共存的关系。容器查询特性的出现,我们可以不再局限于视窗断点来调整布局或UI样式,还可以基于容器断点来调整布局或UI。换句话说,媒体查询是一种宏观的布局(Macro Layout),可以用于整体页面布局;而容器查询可以调整组件的每个元素,创建了一种微观的布局(Micro Layout)。

容器查询解决的是什么问题?

众所周知,响应式设计的概念的核心是 CSS 媒体查询的出现,它允许开发者根据浏览器视窗的尺寸来设置各种样式规则。也正因此,响应式设计和CSS媒体查询开启了更多的 Web 布局解决方案,以及多年来围绕响应视窗尺寸创建的最佳实践。而且,近些年来,设计系统和组件库也得到了更广泛的普及。对于更多开发者而言,更大的期望是:

一次建成,随地部署!

这也意味着一个单独开发的 Web 组件可以在任何情况下工作,以使建立复杂的界面更加有效和一致。只不过,这些组件会组合在一起,形成一个Web页面或Web应用界面。目前,在只有媒体查询的情况下,往往需要额外的一层来协调跨视窗大小变化的组件的突变。在这些情况下,你可能不得不在更多的断点下使用更多的类名来设置不同的样式规则。甚至更惨的是,即使这样做仍然很多情况之下也无法达到最理想的UI表面。

很多时候,响应式Web设计不是关于浏览器视窗尺寸而是关于容器的尺寸大小,比如:

庆幸的是,CSS容器查询的出现,使我们超越了只考虑浏览器视窗尺寸的范围,并允许任何组件或元素对定义的容器尺寸做出响应。因此,虽然你可能仍然使用响应式来给Web页面布局,但Web页面的任何一个组件都可能通过容器查询来定义自己的样式变化。然后,它可以根据它是在一个窄的还是宽的容器中显示,来调整它的样式。

容器查询使我们不再只考虑浏览器视窗尺寸大小,而是允许任何组件或元素对定义的容器尺寸做出响应!

也就是说,有了CSS容器查询,你就能以一种非常精确和可预测的方式定义一个组件的全部样式。

设计时考虑容器查询

虽然响应式设计给Web设计师带来了更多的可有性,但响应式设计还是有很多的局限性。对于Web设计师而言,更期待的是能够根据组件容器尺寸来提供不同的设计风格。依旧拿卡片组件来举例:

也就是说,CSS容器查询特性来了之后,作为一名Web设计师,在设计Web页面(或组件)时,就需要基于容器尺寸考虑如何设计。这样一来,可以向Web开发人员提供组件的细节和变化,Web开发人员也可以基于这些细节进行编码(进行开发)。

不过,这并不意味着容器查询特性之后响应式设计是就失去了意义。在未来,容器查询和响应式设计是共存的,简单地说,Web设计师在设计组件时可能会将组件分为以下几个部分:

  • 基于视窗(CSS媒体查询)
  • 基于容器(CSS容器查询)
  • 通用型(不受影响的组件)

比如:

在未来,Web设计师给Web开发者投喂的设计稿可能就会像下图这样了:

或许因为容器查询的到来,设计师在设计Web的时候,也可能会做出相应的调整。投喂给Web开发的设计稿也可能会和以往的模式有所差异。那么这个时候,Web开发者就需要具备正确理解设计师的意图了。比如,Web设计师可能在未来的设计中会提供向下图的卡片组件设计:

作为Web开发人员,看到上图设计效果,需要改变以往对设计图意图的理解,不能继续执着于基于视窗尺寸来调整组件UI。

上图是基于视窗的一种开发模式,需要为卡片组件设置不同的类名,并且基于视窗尺寸,在相应的类名下调整卡片组件UI。有了容器特性时,我们可以基于现代的Web布局技术,比如Flexbox或Grid布局,让卡片组件基于其容器来调整其UI:

正如上图所示,可以基于视窗大小采用CSS媒体查询特性,Flexbox或Grid布局等技术改变卡片容器.card__container的大小,从而让卡片组件根据其容器尺寸大小做出相应响应。

拥有一个能根据其父容器尺寸做出响应(UI调整)的组件是非常有用的,正如你看到的,我们可以只构建一个组件,就可以满足不同视窗布局下的设计诉求!

容器查询不应该让组件变得复杂化

组件是有由很多个元素组合在一起构成的:

虽然容器查询特性到来,可以让组件根据其容器尺寸来做出响应,但要记住的是,做出响应变化应该要有一个度。如果过度设计的话,对于Web开发人员而言,与其使用容器查询特性来实现UI响应,还不如重新构建一个独立的全新组件。拿用户信息组件(UserProfile)为例,组件内部结构保持不变,或者至少不会增加新的结构,只需稍加调整,比如调整布局就可以实现不同的UI效果,或者让内部元素显示隐藏切换等。在这种情景之中,采用容器查询特性才能显现其魅力:

作用域样式

为了完善容器查询特性,CSS 工作组还在积极讨论作用域样式(Scoped Styles),以帮助为组件提供适当的命名空间来避免冲突。

作用域样式允许传递和特定于组件的样式,以避免命名冲突,许多框架和插件(如CSS模块)已经允许我们在框架内这样做。这个规范现在允许我们用可读的CSS为组件编写本机封装的样式,而无需调整标记。

/* @scope (<root>#) [to (<boundary>#)]? { … } */

@scope (.tabs) to (.panel) {
    :scope { 
        /* targeting the scope root */ 
    }

    .light-theme :scope .tab { 
        /* contextual styles */ 
    }
}

我自己对作用域样式也了解的不怎么多,所以在这里不做过多阐述,以免错误不断。

作用域样式可以通过不同命名空间样式传递给特定的组件,以避免命名冲突。其实在 CSS 中另一个与容器查询同样被受期待的特性,级联分层,即 @layer 也可以用来解决命名冲突,样式冲突的问题。该特性已得到了 Safari 和 Chrome 浏览器支持。如果你对该话题感兴趣的话,可以阅读《初探 CSS 的级联层(@layer》一文。

响应外形的需求

新一代响应式Web设计除了响应用户需求,容器需求之外,还有另一个响应需求,那就是外形的响应需求。

什么是外形响应需求呢?

折叠设备在市场上已经存在了近三年,你可能已经接触过像下图这样的一些设备:

大致主要分为两种类型,双屏可折叠设备(如 Microsoft Surface Duo)和单屏可折叠设备(如 Huawei Mate XS):

在多屏幕或可折叠设备上,Web应用或Web页面在这些设备上的打开姿势也将会有所不同,应用可以单屏显示,也可以跨屏显示:

换句话说,我们的应用或页面要具备这种跨越屏幕的能力,也要具备响应这种跨越的能力,以及还可能需要具备逻辑分隔内容的能力等。

可以说,多屏幕或折叠屏设备开启了更广阔的屏幕空间以及用独特的姿势将用户带入到另一个世界。针对于这种设备,除了用户之外,对于UI设计师,用户体验师和Web开发人员都需要重新面临解锁前所未有的Web体验。这也将是近十年来,Web开发带来最大的变化之一,以及开发人员所要面临的最大挑战之一。

在这里我们针对多屏幕和折叠屏设备的响应,就称之为响应外形的需求。这也是响应式 Web 设计的一部分。

由于可折叠设备相对来说是新型设备,面对这些新型设备时很多开发者并没有做好相应的知识储备,甚至是不知道从何入手。事实上呢?有些Web开发者已经开始在为我们制定这方面的API,除了文章开头提到的三星(Samsung)@Diego González英特尔(Intel Corporation)@Kenneth Rohde Christiansen 之外还有微软(Microsoft)@Bogdan Brinza@Daniel Libby@Zouhir Chahoud。只不过对于Web开发者来说,现在这些制定的规范(CSS相关的特性)和Web API(JavaScript API)还很新,不确定因素过多,甚至差异性也比较大。

到目前为止主要分为两个部分。其中一个部分是《可用于双屏幕和折叠屏的Web API》介绍的相关API,它是由微软(Microsoft)的 @Bogdan Brinza、@Daniel Libby和@Zouhir Chahoud一起制定的,更适用于“有缝”的折叠处设备;另一部分是目前处于W3C规范ED阶段的 屏幕折叠 API ,它更适用于“无缝”的折叠设备。

@argyleink在Github上发起了一个使用CSS媒体特性来检测折叠屏的讨论。也就是说,Web开发者可以使用@media相关的特性来识别折叠屏,为折叠屏的类型(比如“有缝”和“无缝”)提供相应的媒体查询。

比如,我们可以使用 screen-spanning 这个特性可以用来帮助Web开发人员检测“根视图”是否跨越多个相邻显示区域,并提供有关这些相邻显示区域配置的详细信息。

也可以使用 screen-fold-posturescreen-fold-angle 两个媒体查询来对无缝设备进行查询:

还可以使用 horizontal-viewport-segmentsvertical-viewport-segments 查询视口的数量:

horizontal-viewport-segmentsvertical-viewport-segments 是最新的两个查询特性,它们将替代最初的 screen-spanning 这个媒体查询特性!

除此之外,还可以通过一些折叠姿势来进行查询:

除了CSS媒体查询之外,还引入了六个新的CSS环境变量,以帮助开发者计算显示区域的几何形状,计算铰链区域被物理特征遮挡的几何形状:

上图中展示的这六个CSS环境变量将替代以前的 env(fold-top)env(fold-left)env(fold-width)env(fold-height)

对于Web开发者来说,我们可以像下面这样来使用:

// 有缝折叠
@media (spanning: single-fold-vertical) { 
    // CSS Code...
}

// 无缝折叠
@media (screen-fold-posture: laptop){
    // CSS Code...
}

// 折叠角度查询
@media (max-screen-fold-angle: 120deg) {
    // CSS Code...
}

// 视口数量查询
@media (horizontal-viewport-segments: 2) {
    // CSS Code...
}
@media (vertical-viewport-segments: 2) {
    // CSS Code...
}

在现代布局中,将这些媒体查询特性、CSS环境变量和CSS Grid布局结合在一起,就可以很轻易的满足外形响应的需求变化。比如:

:root {
    --sidebar-width: 5rem;
}

@media (spanning: single-fold-vertical) {
    :root {
        --sidebar-width: env(viewport-segment-left 0 0);
    }
}

main {
    display: grid;
    grid-template-columns: var(--sidebar-width) 1fr;
}

Stephanie 在她的最新博文《Building Web Layouts For Dual-Screen And Foldable Devices》中也向大家提供了一个示例,演示了按屏幕数量(horizontal-viewport-segments: 2)查询的示例

.recipe {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    grid-template-rows: minmax(175px, max-content);
    grid-gap: 1rem;
}

.recipe-meta {
    grid-column: 1 / 4;
}

img {
    grid-column: 1 / 4;
}

.recipe-details__ingredients {
    grid-row: 3;
}

.recipe-details__preparation {
    grid-column: 2 / 4;
    grid-row: 3;
}

@media (horizontal-viewport-segments: 2) { 
    .recipe {
        grid-template-columns: env(viewport-segment-width 0 0) 1fr 1fr;
        grid-template-rows: repeat(2, 175px) minmax(175px, max-content);
    }

    .recipe-meta {
        grid-column: 1 / 2;
    }

    img {
        grid-column: 2 / 4;
        grid-row: 1 / 3;
    }

    .recipe-details__ingredients {
        grid-row: 2;
    }
    
    .recipe-details__preparation {
        grid-column: 2 / 4;
        grid-row: 3; 
    }

}

上面是从示例中截取的有关于布局的关键代码。最终效果如下:

如果你对多屏或折叠屏这方面技术感兴趣的话,还可以阅读:

技术的变革是永无止境的,我们将来要面对的用户终端也绝不会仅现于目前能看到的终端设备和媒介,就好比现在运用于游戏行业的 VR(虚拟现实)和 AR(增强现实)设备。虽然现在 VR 和 AR 用于其他行业的场景还很少见,但我们可以预见,在 VR 和 AR 设备越来越成熟和更多的设备发布之后,我们就能看到 VR 和 AR 就像我们已经看到几十年前的触摸屏设备一样。或许有一天,你设计(或开发)的Web页面或应用就需要能在 VR 和 AR 设备上有一个较好的呈现。

上图来自于《UX Case Study: Metaverse Banking VR / AR Design Concept of the Future》一文。UXDA的专业金融用户体验架构师和设计师团队向您介绍第一个混合现实银行概念,包括VR和AR银行设计、平板电脑、可穿戴设备、桌面和移动银行UI / UX。

在这里,我想表达的是,未来的响应式 Web 设计要响应的外形需求可能会更丰富,更复杂。

总结

响应式 Web 设计已经将 Web 带到了今天人们所能接触到的每一个连接的屏幕上。Web设计师和创意开发者用创造性的思维、大胆的想法和某种无畏的精神探索、测试和迭代他们的想法,使在线体验更有吸引力、更容易访问和更智能,推动了设计方法的发展。就好比这里所提到的 组件驱动式Web设计

组件驱动式 Web 设计的到来或者说 CSS 容器查询、作用域样式、级联层控制等特性的出现,这些先进的特性使我们有机会从页面布局、全局样式和用户样式中孤立组件样式,从而实现更具弹性的响应式设计。这意味着你现在可以使用基于页面的媒体查询设计宏观布局,包括多屏或折叠屏的细微差异;同时使用基于容器查询给组件设计微观上布局,并添加基于用户偏好的媒体查询,来实现基于用户的独特偏好和需求的定制化体验。

这就是下一代响应式 Web 设计,也就是 组件驱动式 Web 设计(或开发)。它结合了宏观布局和微观布局,最重要的是,也将用户定制化和尺寸外形都考虑到了。

这些变化中的任何一个都将构成我们对web设计方式的重大转变。但它们结合在一起,意味着我们甚至在概念化响应性设计方面的一个巨大转变。是时候思考不止步于视口大小的响应性设计了,并开始考虑所有这些新方向,来获得更好的基于组件和定制化的体验。

也就是说。如果我们将这些组件驱动的功能纳入设计系统,并从整体上改变我们对待 Web 设计的方式,我们就可以利用这些功能以及更多的功能来改善每一个登陆你网站的访问者的用户体验。我们可以为他们提供真正个性化的体验,提高参与度和转化率,并最终提高用户对你的品牌的感知。

我们不再是为用户群体设计。我们对 "受众"一词的理解将发生变化,因为内容和体验将为一个人而不是许多人的受众而变得高度集中。

组件驱动的响应式 Web 设计将使 Web 真正的可移植,并能适应甚至还没有发明的设备。与其在今天的技术范围内追赶和设计,我们将只为用户设计。

最后,希望文章中提到的概念和技术对你有所帮助。