《 Responsive Web Design》(地址:https://alistapart.com/article/responsive-web-design/)
《2022 年的 CSS》(地址:https://www.w3cplus.com/css/what-is-new-css-in-2022.html)
《新的响应式:组件驱动式 Web 设计》(地址:https://io.google/2021/session/a1760fa3-879a-4e98-a616-994ca8d3aab5/?lng=zh-CN)
响应式Web设计的发展历程
既然要聊响应式Web设计,那么我们就花一点篇幅和时间简单地了解一下其发展历程。
众所周知,自从 Tim Berners-Lee 创建第一个 Web页面(大约在1991年8月份左右)到90年代末,Web页面都是非常简陋的:
直到90年代末20年代初,Web设计和用户体验随着 CSS 的到来才慢慢地有了美感,Web页面看起来开始像我们今天使用的网站:
Liquid Page Layout Example@nickpettitCodePen
(地址:https://codepen.io/nickpettit/pen/dyZwVg)
(地址:https://codepen.io/nickpettit)
(地址:https://codepen.io/)
但流式布局并不是完美的。使用流式布局的Web页面上,内容可能会溢出,在较小的屏幕上文字可能会换行,在较大的屏幕上可能会有很多不必的空白。
800px x 600px
到 1280px
宽或更大的屏幕分辨率下看起来还不错。然而,如果我们能把它分割得更细一些,比如为 800px ~ 1024px
、1024px ~ 1280px
以及1280px
以上的分辨率提供不同的定制布局,那效果会更佳。同样,对于 640px ~ 800px
、320px ~ 640px
、240px ~ 320px
以及 240px
以下的分辨率也可以定制不同的布局。<!-- 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
属性的值分别为 narrow
、default
和 wide
,并且在 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》详细介绍该技术。
如此一来,开发人员要开发两个版本,相应的工作量就更大了,特别对于要快速响应和试错的Web应用来说,难度变得更大。
Web开发人员为了能改善这种现象,在 2010 年的时候,Ethan Marcotte(@beep)基于 John Allsopp(@johnallsopp)的 《网页设计的道(A Dao of Web Design)》(地址:https://alistapart.com/article/dao/),提出响应式Web设计思路(《Responsive Web Design》)(地址:https://alistapart.com/article/responsive-web-design/)。从此响应式Web设计(Responsive Web Design,简称 RWD)的身影就出现在了公众面前。
Ethan Marcotte(@beep)在《Responsive Web Design》(地址:https://alistapart.com/article/responsive-web-design/)中提到,响应式这个词源自于建筑学领域,原本指的是建筑物本身会“响应”实际的使用情况,来自我调整。在Web开发领域,“响应式”的意思就变成了,我们开发的Web页面会“响应”用户的设备尺寸而自动调整布局。在这篇文章中提到过,我们可以基于 流体网格(Fluid Grids)、灵活的图片(Flexible Images)和媒体查询(Media Queries) 三种技术来构建一个响应式Web网站或Web应用。
另外,Ethan Marcotte(@beep)构建了第一个具有响应式的Web网页,可以说是响应式Web设计经典案例之一(只可惜现在打不开了):
@container
、级联分层@layer
以及 CSS作用域 @scope
等。
随着用户偏好查询、容器查询以及其他设备类型查询的出现(CSS新特性),Web社区即将进入响应式 Web 设计的新时代,并改变我们对其含义的看法。
响应式Web设计的现状
我们来看下简化后的不同版本的设计线框图,如下所示:
在上图中,设计师为卡片(Card)提供了三种不同的 UI 效果。虽然卡片在不同设备视窗下有着不同的UI效果,但他们构成的元素是相同,都有卡片容器、卡片缩略图、卡片标题 和 卡片描述等:
/* 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
或更大的视窗宽度下才会生效。
700px
或更大的视窗宽度中,.card--vertical
和 .card--featured
都有可能出现这样的场景。700px
或大于700px
时,卡片组件从默认的.card
(水平)状态切换到垂直(.card--vertical
)状态。也就是说,如果我们想在小于700px
宽度的视窗下,使用垂直状态(.card--vertical
)卡片效果是不行的:正如上图所示,如果只有一张卡片数据吐出的时候,整个卡片宽度会扩展与容器宽度相等(拉伸)。此时,卡片宽度太宽导致用于卡片上的缩略图被拉抻,有可能会使缩略图变得模糊。
事实上呢?这只是Web开发者的一厢情愿,设计师真正的意图可能是像下图这样:
下一代响应式Web设计
今天,当我们提到响应式Web设计时,首先想到的是Ethan Marcotte(@beep)的 《Responsive Web Design》(地址:https://alistapart.com/article/responsive-web-design/)博文中提到过的基于 流体网格(Fluid Grids)、灵活的图片(Flexible Images)和媒体查询(Media Queries) 三种技术来构建一个适应不同屏幕尺寸或不同移动终端设备的 Web 页面。
为了理解这种开发模式的转变,并为即将到来的变化浪潮做好准备,让我们看看在响应式Web设计运动中我们可以期待的变化,以及这可能会如何改变我们对待响应式设计的概念。
▐ 响应用户的需求
min-width
、max-width
、min-height
、max-height
、orientation
和 aspect-ratio
等)比较熟悉,比如:@media (max-width: 45rem) {
/* 视窗小于 45rem */
}
@media (min-width: 45rem) {
/* 视窗大于 45rem */
}
@media
最基础的一部分规则,事实上,@media
规则大约包含了 24
个可供查询的特性,其中大约 19
个查询规则得到较好的支持,详细的可以阅读《图解CSS: CSS媒体查询》(地址:https://www.w3cplus.com/css/css-media-queries-guide.html)一文。在这些新增的查询特性中是用来改善用户体验的,比如 Media Queries Level 5(地址:https://www.w3.org/TR/mediaqueries-5/#mf-user-preferences)规范中的第十一部分,能够让你根据用户自身的特定偏好和需求来设计 Web 体验。CSS媒体查询提供了一些用户喜好的查询特性,这些特性可以识别出用户在系统上的偏好设置,帮助Web开发者构建更加健壮和个性化的 Web 体验,特别是对于那些具有可访问性需求的用户。
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;
}
}
“并不是每一个可以访问网络的设备都可以呈现动画,或者流畅地呈现动画。”
prefers-reduced-motion
和 update
结合在一起使用:@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-duration
或transition-duration
声明的动画以人眼难以察觉的速度结束。当一个人要求减少动效体验,或者设备有一个刷新率较低的屏幕,比如电子墨水或廉价的智能手机,它就能发挥作用。
但需要注意的是,使用动态减弱并不意味着“没有动效”,因为动效在Web页面中传达信息能起到至关重要的作用。相反,你应该使用一个坚实的、去除非必须的动效基础体验去引导这些用户,同时逐步增强没有此项偏好设置的其他用户的体验。
如果你对减弱动效效果这方面技术感兴趣的话,还可以阅读:
prefers-reduced-motion
, the reduced motion media queryprefers-reduced-motion
(地址:https://hidde.blog/meeting-2-22-pause-stop-hide-with-prefers-reduced-motion/)
(地址:https://hidde.blog/meeting-2-22-pause-stop-hide-with-prefers-reduced-motion/)
使用 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>
的name
为theme-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
)主题,并且优先选择深色主题。<fieldset>
元素的背景颜色是如何根据是否启用了深色模式而改变的,它遵循了开发者在页面上提供的内联样式表的规则。它要么是gainsboro
,要么是darkslategray
。上图是亮色模式(light
)下,由开发者和用户代理指定的样式。根据用户代理的样式表,文本是黑色的,背景是白色的。<fieldset>
元素的背景颜色是gainsboro
,由开发者在内联的式表中指定的颜色。
dark
)下,由开发者和用户代理指定的样式。根据用户代理的样式表,文本是白色的,背景是黑色的。<fieldset>
元素的背景色是darkslategray
,由开发者在内联样式表中指定的颜色。<button>
元素的外观是由用户代理样式表控制的。它的颜色被设置为ButtonText
系统颜色,其背景颜色和边框颜色被设置为ButtonFace
系统颜色。<button>
元素的边框颜色是如何变化的。border-top-color
和border-bottom-color
的计算值从rgba(0,0,0,.847)
(偏黑)切换到rgba(255, 255, 255, .847)
(偏白),因为用户代理根据颜色方案动态地更新ButtonFace
。同样适用于<button>
元素的color
属性,它被设置为相应的系统颜色ButtonText
。系统偏好设置的那些事儿
(地址:https://www.w3cplus.com/css/css-system-things.html)
Windows High Contrast Mode, Forced Colors Mode And CSS Custom Properties
(地址:https://www.smashingmagazine.com/2022/03/windows-high-contrast-colors-mode-css-custom-properties/)
Styling for Windows high contrast with new standards for forced colors
(地址:https://blogs.windows.com/msedgedev/2020/09/17/styling-for-windows-high-contrast-with-new-standards-for-forced-colors/)
Operating System and Browser Accessibility Display Modes
(地址:https://www.a11yproject.com/posts/operating-system-and-browser-accessibility-display-modes/)
不是每个人都能幸运地拥有快速、可靠或无限的数据(流量)套餐。
你可能有过出差旅行的经历,也可能碰到了手机数据不够用,那么访问一个重图片的网站是很糟糕的(虽然说现在流量对于大家来说不是很大的事情,花钱总是能摆平的)。不过,一旦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上的应用程序减少网络数据的使用:
<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
媒体查询主要用于检测用户是否要求系统增加或减少相邻颜色之间的对比度。比如一些喜欢阅读电子书的用户,在阅读与文本背景对比度相差不大的文本时会遇到困难,他们更喜欢较大的对比度,利于阅读。
比如像下面这个示例:
.contrast {
background-color: #0958d8;
color: #fff;
}
@media (prefers-contrast: high) {
.contrast {
background-color: #0a0db7;
}
}
从W3C规范中不难发现,规范中提供了六个有关于用户偏好的媒体查询特性:
forced-colors
的示例,该示例来自于 Eric 的 《Windows High Contrast Mode, Forced Colors Mode And CSS Custom Properties》一文: Modal dialog with Forced Color mode tweaks@smashingmagCodePen (地址:https://codepen.io/smashingmag/pen/zYPVjPa) (地址:https://codepen.io/smashingmag) (地址:https://codepen.io/)
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
),所以我们不能在这种专门的浏览模式下依赖这种视觉效果:
Media features
(地址:https://web.dev/learn/design/media-features/)
CSS媒体查询新特性
(地址:https://www.w3cplus.com/css/new-css-media-queries.html)
系统偏好设置的那些事儿
(地址:https://www.w3cplus.com/css/css-system-things.html)
图解CSS: CSS媒体查询
(地址:https://www.w3cplus.com/css/css-media-queries-guide.html)
CSS 的 媒体查询引发了一场响应式设计的革命,为开发者提供了一种方法来查询用户代理或设备环境的各个方面,比如视窗的大小或用户偏好来改变 Web 页面的风格。直到现在,媒体查询还做不到让元素的样式能根据一个最近的容器的大小来改变样式风格。也正因此,大家一直期待的容器查询来了。
很难想象,从基于页面的响应式设计(媒体查询)到基于容器的响应式设计(容器查询)的转变,对设计生态系统的发展会起到什么作用?
自 2010 年 @Ethan Marcotte首次提出 响应式Web设计(RWD)(地址:https://alistapart.com/article/responsive-web-design/)的设计理念(概念),Web设计就进入现代Web布局时代。开发者可以根据 CSS 媒体查询特性 (通常是视窗宽度、媒体设备特性等)来为Web页面定制不同的表现形式,比如可以根据用户浏览内容的设备特性来呈现不同的布局、不同的字体大小和不同的图片等。
<picture>
元素,将响应式图片带到了响应式 Web 设计的世界,他在《Container Queries: Once More Unto the Breach》(地址:https://alistapart.com/article/container-queries-once-more-unto-the-breach/)一文中概述了元素查询的挑战和使用案例演示了容器查询的特性。@container
方法(地址:https://github.com/oddbird/css-sandbox/blob/main/src/rwd/query/explainer.md)(地址:https://github.com/oddbird/css-sandbox/blob/main/src/rwd/query/explainer.md)。@container
方法通过对被查询的元素应用大小和布局的限制来实现。任何具有尺寸和布局限制的元素都可以通过一个新的 @container
规则进行查询,其语法与现有的媒体查询类似。虽然 CSS Containment Module Level 3 还是 FPWD 版本,规范中所描述的语法不是最终版本,直到写这篇文章,其语法规则还在变,因此文章中所展示的语法有可能会变以及相关的示例有一天就无效了:
CSS 容器查询最大的特点是:
容器查询允许开发者定义任何一个元素为包含上下文,查询容器的后代元素可以根据查询容器的大小或计算样式的变化来改变风格!
container-type
或 container
)指定要能的查询类型来建立的。适用于其后代的样式规则可以通过使用@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 {
// ...
}
}
Untitled@airenCodePen
(地址:https://codepen.io/airen/pen/ZEvoBYL)
(地址:https://codepen.io/airen)
(地址:https://codepen.io/)
拖动卡片右下角的滑块,改变 .card__container
容器大小,你可以看到卡片组件(.card
)UI效果的变化:
@container
规则,其工作方式与使用@media
的媒体查询类似,但相反,@container
查询父容器以获取信息,而不是视口和浏览器的UserAgent
。
初探CSS容器查询
(地址:https://www.w3cplus.com/css/container-queries.html)
容器查询给设计带来的变化
(地址:https://www.w3cplus.com/css/container-queries-for-design.html)
容器查询中的 container
和 @container
(地址:https://www.w3cplus.com/css/container-queries-with-container-and-at-container.html)
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-type
是 container
属性中的一个子属性,另外,还可以显式使用 container-name
来命名你的容器,即给一个包含性上下文指定一个具体的名称:.card__container {
container-name: card
}
container
,只不过需要在 container-type
和 container-name
之间添加斜杠分割符/
:.card__container {
container-type: inline-size;
container-name: card;
}
/* 等同于 */
.card__container {
container: inline-size / card;
}
如果一个容器查询被应用到一个没有定义的包含祖先元素上,查询将无法应用。也就是说,无论是 body
还是 html
元素,都没有默认的回退包含上下文。另外,定义包含上下文名称时不能是 CSS 的关键词,比如 default
、inherit
、initial
等。
注意:container-name
可以省略,如果省略将会使用其初始值none
,但 container-type
不可省略,如果省略的话则表示未显式声明包含性上下文!
container
(或其子属性 container-type
和container-name
)对一个元素显式声明包含上下文(对一个元素应用包含性)。@
规则@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
)属性进行查询。
style()
函数对样式进行查询:@container style(--card: large) {
/* CSS Style */
}
@container size(width > 30em) and style(--card: large) {
/* CSS Style */
}
@container
的查询条件(width > 30em)
相当于 (min-width: 30em)
。使用数学表达式要比使用 min-width
或max-width
更易于理解,自 Media Queries Level 4(地址:https://www.w3.org/TR/mediaqueries-4/#mq-range-context)开始, 在 @media
规则中,也可以使用我们熟悉的数学表达式,比如>=
、<=
等来替代以往不易于理解的min-
和max-
:
container
和 @container
,但他们并不是指的同一个属性,前者是一个CSS属性,后者是一个CSS代码块。而且两者有本质的区别:container
是 container-type
和 container-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》一文。 (地址:https://ishadeed.com/article/the-state-of-mobile-first-and-desktop-first/)
<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;
}
Untitled@airenCodePen (地址:https://codepen.io/airen/pen/MWrXNGM) (地址:https://codepen.io/airen) (地址:https://codepen.io/)
有了这样一个卡片组件之后,如果将其放在不同的位置,即使是同一页面,同一视窗断点下,也会根据其容器断点自动匹配最为适合的布局(或UI效果)。比如:
Untitled@airenCodePen (地址:https://codepen.io/airen/pen/WNdKgMK) (地址:https://codepen.io/airen) (地址:https://codepen.io/)
通过上面的示例的介绍,我想你对容器查询特性已经有了一个较清晰的认识了。从使用角度来看,容器查询和媒体查询是非常的相似,那么有人可能会问,有了容器查询是不是就不再需要媒体查询特性了呢?在回答这个问题之前,我们简单的来看两者的差异。
众所周知,媒体查询查询的浏览器视窗宽度(当然还有其他查询特性),而容器查询查询的是组件其父容器(具有包含性上下文的祖先元素)的宽度(或样式)。下图可能可以清晰的阐述两者的差异:
就我个人认为,两者不是谁替代谁的关系,更应该是两者共存的关系。容器查询特性的出现,我们可以不再局限于视窗断点来调整布局或UI样式,还可以基于容器断点来调整布局或UI。换句话说,媒体查询是一种宏观的布局(Macro Layout),可以用于整体页面布局;而容器查询可以调整组件的每个元素,创建了一种微观的布局(Micro Layout)。
众所周知,响应式设计的概念的核心是 CSS 媒体查询的出现,它允许开发者根据浏览器视窗的尺寸来设置各种样式规则。也正因此,响应式设计和CSS媒体查询开启了更多的 Web 布局解决方案,以及多年来围绕响应视窗尺寸创建的最佳实践。而且,近些年来,设计系统和组件库也得到了更广泛的普及。对于更多开发者而言,更大的期望是:
一次建成,随地部署!
这也意味着一个单独开发的 Web 组件可以在任何情况下工作,以使建立复杂的界面更加有效和一致。只不过,这些组件会组合在一起,形成一个Web页面或Web应用界面。目前,在只有媒体查询的情况下,往往需要额外的一层来协调跨视窗大小变化的组件的突变。在这些情况下,你可能不得不在更多的断点下使用更多的类名来设置不同的样式规则。甚至更惨的是,即使这样做仍然很多情况之下也无法达到最理想的UI表面。
很多时候,响应式Web设计不是关于浏览器视窗尺寸而是关于容器的尺寸大小,比如:
虽然响应式设计给Web设计师带来了更多的可有性,但响应式设计还是有很多的局限性。对于Web设计师而言,更期待的是能够根据组件容器尺寸来提供不同的设计风格。依旧拿卡片组件来举例:
基于视窗(CSS媒体查询)
基于容器(CSS容器查询)
通用型(不受影响的组件)
或许因为容器查询的到来,设计师在设计Web的时候,也可能会做出相应的调整。投喂给Web开发的设计稿也可能会和以往的模式有所差异。那么这个时候,Web开发者就需要具备正确理解设计师的意图了。比如,Web设计师可能在未来的设计中会提供向下图的卡片组件设计:
上图是基于视窗的一种开发模式,需要为卡片组件设置不同的类名,并且基于视窗尺寸,在相应的类名下调整卡片组件UI。有了容器特性时,我们可以基于现代的Web布局技术,比如Flexbox或Grid布局,让卡片组件基于其容器来调整其UI:
正如上图所示,可以基于视窗大小采用CSS媒体查询特性,Flexbox或Grid布局等技术改变卡片容器.card__container
的大小,从而让卡片组件根据其容器尺寸大小做出相应响应。
拥有一个能根据其父容器尺寸做出响应(UI调整)的组件是非常有用的,正如你看到的,我们可以只构建一个组件,就可以满足不同视窗布局下的设计诉求!
组件是有由很多个元素组合在一起构成的:
虽然容器查询特性到来,可以让组件根据其容器尺寸来做出响应,但要记住的是,做出响应变化应该要有一个度。如果过度设计的话,对于Web开发人员而言,与其使用容器查询特性来实现UI响应,还不如重新构建一个独立的全新组件。拿用户信息组件(UserProfile
)为例,组件内部结构保持不变,或者至少不会增加新的结构,只需稍加调整,比如调整布局就可以实现不同的UI效果,或者让内部元素显示隐藏切换等。在这种情景之中,采用容器查询特性才能显现其魅力:
为了完善容器查询特性,CSS 工作组还在积极讨论作用域样式(Scoped Styles)(地址:https://css.oddbird.net/scope/),以帮助为组件提供适当的命名空间来避免冲突。
/* @scope (<root>#) [to (<boundary>#)]? { … } */
@scope (.tabs) to (.panel) {
:scope {
/* targeting the scope root */
}
.light-theme :scope .tab {
/* contextual styles */
}
}
@layer
也可以用来解决命名冲突,样式冲突的问题。该特性已得到了 Safari 和 Chrome 浏览器支持。如果你对该话题感兴趣的话,可以阅读《初探 CSS 的级联层(@layer
)》(地址:https://www.w3cplus.com/css/css-layer.html)一文。
新一代响应式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》(地址:https://www.w3cplus.com/mobile/introducing-dual-screen-foldable-web-apis.html)介绍的相关API,它是由微软(Microsoft)的 @Bogdan Brinza、@Daniel Libby和@Zouhir Chahoud一起制定的,更适用于“有缝”的折叠处设备;另一部分是目前处于W3C规范ED阶段的 屏幕折叠 API(地址:https://w3c.github.io/device-posture/),它更适用于“无缝”的折叠设备。
@argyleink在Github上发起了一个使用CSS媒体特性来检测折叠屏的讨论(地址:https://github.com/w3c/csswg-drafts/issues/4141)。也就是说,Web开发者可以使用@media相关的特性来识别折叠屏,为折叠屏的类型(比如“有缝”和“无缝”)提供相应的媒体查询。
比如,我们可以使用 screen-spanning
这个特性可以用来帮助Web开发人员检测“根视图”是否跨越多个相邻显示区域,并提供有关这些相邻显示区域配置的详细信息。
也可以使用 screen-fold-posture
和 screen-fold-angle
两个媒体查询来对无缝设备进行查询:
还可以使用 horizontal-viewport-segments
和 vertical-viewport-segments
查询视口的数量:
horizontal-viewport-segments
和 vertical-viewport-segments
是最新的两个查询特性,它们将替代最初的screen-spanning
这个媒体查询特性!
除此之外,还可以通过一些折叠姿势来进行查询:
除了CSS媒体查询之外,还引入了六个新的CSS环境变量,以帮助开发者计算显示区域的几何形状,计算铰链区域被物理特征遮挡的几何形状:
env(fold-top)
、env(fold-left)
、env(fold-width)
和env(fold-height)
。// 有缝折叠
@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》(地址:https://www.smashingmagazine.com/2022/03/building-web-layouts-dual-screen-foldable-devices/)中也向大家提供了一个示例,演示了按屏幕数量(horizontal-viewport-segments: 2
)查询的示例(地址:https://www.stephaniestimac.com/demos/smashing-ds-demo/):
.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;
}
}
上面是从示例中截取的有关于布局的关键代码。最终效果如下:
聊聊安卓折叠屏给交互设计和开发带来的变化
(地址:https://www.w3cplus.com/mobile/mobile-folding.html)
可折叠Web可能会给我们带来的变化
(地址:https://www.w3cplus.com/mobile/css-foldable-display.html)
可用于双屏幕和折叠屏的Web API
(地址:https://www.w3cplus.com/mobile/introducing-dual-screen-foldable-web-apis.html)
折叠屏相关的Web API
(地址:https://www.w3cplus.com/mobile/introducing-dual-screen-foldable-web-apis.html)
Foldable CSS and JavaScript update for web developers
(地址:https://devblogs.microsoft.com/surface-duo/foldable-css-javascript-edge-96/)
Building Web Layouts For Dual-Screen And Foldable Devices
(地址:https://www.smashingmagazine.com/2022/03/building-web-layouts-dual-screen-foldable-devices/)
总结
响应式 Web 设计已经将 Web 带到了今天人们所能接触到的每一个连接的屏幕上。Web设计师和创意开发者用创造性的思维、大胆的想法和某种无畏的精神探索、测试和迭代他们的想法,使在线体验更有吸引力、更容易访问和更智能,推动了设计方法的发展。就好比这里所提到的 组件驱动式Web设计。
组件驱动式 Web 设计的到来或者说 CSS 容器查询、作用域样式、级联层控制等特性的出现,这些先进的特性使我们有机会从页面布局、全局样式和用户样式中孤立组件样式,从而实现更具弹性的响应式设计。这意味着你现在可以使用基于页面的媒体查询设计宏观布局,包括多屏或折叠屏的细微差异;同时使用基于容器查询给组件设计微观上布局,并添加基于用户偏好的媒体查询,来实现基于用户的独特偏好和需求的定制化体验。
这就是下一代响应式 Web 设计,也就是 组件驱动式 Web 设计(或开发)。它结合了宏观布局和微观布局,最重要的是,也将用户定制化和尺寸外形都考虑到了。
这些变化中的任何一个都将构成我们对web设计方式的重大转变。但它们结合在一起,意味着我们甚至在概念化响应性设计方面的一个巨大转变。是时候思考不止步于视口大小的响应性设计了,并开始考虑所有这些新方向,来获得更好的基于组件和定制化的体验。
也就是说。如果我们将这些组件驱动的功能纳入设计系统,并从整体上改变我们对待 Web 设计的方式,我们就可以利用这些功能以及更多的功能来改善每一个登陆你网站的访问者的用户体验。我们可以为他们提供真正个性化的体验,提高参与度和转化率,并最终提高用户对你的品牌的感知。
我们不再是为用户群体设计。我们对 "受众"一词的理解将发生变化,因为内容和体验将为一个人而不是许多人的受众而变得高度集中。
组件驱动的响应式 Web 设计将使 Web 真正的可移植,并能适应甚至还没有发明的设备。与其在今天的技术范围内追赶和设计,我们将只为用户设计。
最后,希望文章中提到的概念和技术对你有所帮助。
团队介绍
我们是F(X) Team团队,F(x) Team ,F(x) 指函数 F(x) ,是机器学习中常出现的符号,深度学习的本质也是求 f(x) 的最优解,意味拥有不同特征的成员经过 fx 团队神奇作⽤,不断“训练”,⼀起找到前端智能化团队的最优解。我们致力于前端智能化领域的探索和实践,赋能淘宝、天猫、聚划算等日常与大促(如双 11 )业务,是淘系前端智能化实践的领路人,也是阿里经济体前端委员会智能化方向的核心团队。我们在 D2C(Design to Code) 领域开放了 Imgcook 平台,逐步释放阿里生态的前端生产力;同时我们也与 Google 的 tensorflow 团队保持长线合作,基于 tfjs-node 之上,开源了我们的前端算法工程框架 Pipcook,引领前端行业向智能化时代迈进。