为未知而设计:响应式布局、CSS容器查询与内容优先策略

本文探讨了响应式设计的演进,从传统媒体查询到CSS容器查询和内在布局,强调内容优先的设计策略,以应对未知设备和多变内容场景,提升产品的适应性和用户体验。

为未知而设计

你公司的生存取决于其产品在你未曾想象的情境中和尚未存在的设备上工作的能力。
——Jeffrey Zeldman

我不确定第一次听到这句话是什么时候,但它多年来一直萦绕在我心头。如何为无法想象的情境创建服务?或者设计能在尚未发明的设备上工作的产品?

Flash、Photoshop 和响应式设计

当我刚开始设计网站时,我的首选软件是 Photoshop。我创建一个 960px 的画布,并开始设计一个布局,之后再将内容放入其中。开发阶段是关于使用固定宽度、固定高度和绝对定位来实现像素级完美精度。

Ethan Marcotte 在 An Event Apart 的演讲以及随后于 2010 年在 A List Apart 上发表的文章《响应式网页设计》改变了这一切。我一听说响应式设计就被说服了,但我也感到恐惧。我之前引以为豪的充满魔数的像素级完美设计不再足够好。

我的第一次响应式设计经历并没有减轻这种恐惧。我的第一个项目是拿一个现有的固定宽度网站并使其响应式。我艰难地学到的是,你不能只是在项目结束时添加响应性。为了创建流动布局,你需要在设计阶段全程规划。

一种新的设计方式

设计响应式或流动站点一直是关于消除限制,生成可以在任何设备上查看的内容。它依赖于使用基于百分比的布局,我最初是通过原生 CSS 和工具类实现的:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
.column-span-6 {
  width: 49%;
  float: left;
  margin-right: 0.5%;
  margin-left: 0.5%;
}

.column-span-4 {
  width: 32%;
  float: left;
  margin-right: 0.5%;
  margin-left: 0.5%;
}

.column-span-3 {
  width: 24%;
  float: left;
  margin-right: 0.5%;
  margin-left: 0.5%;
}

然后使用 Sass,这样我就可以利用 @includes 来重用重复的代码块,并回归更语义化的标记:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
.logo {
  @include colSpan(6);
}

.search {
  @include colSpan(3);
}

.social-share {
  @include colSpan(3);
}

媒体查询

响应式设计的第二个要素是媒体查询。没有它们,内容会缩小以适应可用空间,无论内容是否保持可读性(引入移动优先方法时出现了完全相反的问题)。

媒体查询通过允许我们添加断点来防止这种情况,设计可以在这些断点处适应。像大多数人一样,我从三个断点开始:一个用于桌面,一个用于平板,一个用于移动设备。多年来,我添加了越来越多的断点,用于平板手机、宽屏等。

多年来,我愉快地以这种方式工作,并在此过程中提高了我的设计和前端技能。我遇到的唯一问题是修改内容,因为有了我们的 Sass 网格系统,网站所有者无法在不修改标记的情况下添加内容——这对小企业主来说可能是个难题。这是因为网格中的每一行都是使用 div 作为容器定义的。添加内容意味着创建新的行标记,这需要一定的 HTML 知识。

行标记是早期响应式设计的主要内容,存在于所有广泛使用的框架中,如 Bootstrap 和 Skeleton。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
<section class="row">
  <div class="column-span-4">1 of 7</div>
  <div class="column-span-4">2 of 7</div>
  <div class="column-span-4">3 of 7</div>
</section>

<section class="row">
  <div class="column-span-4">4 of 7</div>
  <div class="column-span-4">5 of 7</div>
  <div class="column-span-4">6 of 7</div>
</section>

<section class="row">
  <div class="column-span-4">7 of 7</div>
</section>

当我从为中小型企业构建网站的设计机构转到更大的内部团队,在那里我负责一套相关站点时,另一个问题出现了。在这些角色中,我开始更多地与可重用组件打交道。

我们对媒体查询的依赖导致组件与常见的视口尺寸绑定。如果组件库的目标是重用,那么这是一个真正的问题,因为你只能在设计的设备与模式库中使用的视口尺寸相对应时使用这些组件——在这个过程中并没有真正达到“尚未存在的设备”的目标。

然后是空间的问题。媒体查询允许组件根据视口大小进行调整,但如果我把一个组件放到侧边栏中,如下图所示呢?

容器查询:我们的救星还是虚假的黎明?

容器查询长期以来一直被吹捧为对媒体查询的改进,但在撰写本文时,大多数浏览器都不支持。有 JavaScript 的变通方案,但它们可能会产生依赖性和兼容性问题。容器查询的基本理论是,元素应该根据其父容器的大小而不是视口宽度来改变,如下图所示。

支持容器查询的最大论点之一是,它们帮助我们创建真正可重用的组件或设计模式,因为它们可以被拾取并放置在布局的任何地方。这是向基于组件的设计形式迈出的重要一步,这种设计可以在任何设备上以任何尺寸工作。

换句话说,用响应式组件取代响应式布局。

容器查询将帮助我们从设计响应浏览器或设备大小的页面转向设计可以放置在侧边栏或主要内容中并相应响应的组件。

我担心的是,我们仍然使用布局来决定设计何时需要适应。这种方法总是有限制性的,因为我们仍然需要预定义的断点。因此,我对容器查询的主要问题是,我们如何决定何时更改组件使用的 CSS?

脱离上下文和真实内容的组件库可能不是做这个决定的最佳场所。

如下图所示,我们可以使用容器查询为特定的容器宽度创建设计,但如果我想根据图像大小或比例更改设计呢?

在这个例子中,容器的尺寸不应该是决定设计的因素;相反,图像才是。

在获得可靠的跨浏览器支持之前,很难确定容器查询是否会成功。响应式组件库肯定会改变我们的设计方式,并提高重用和规模化设计的可能性。但也许我们总是需要调整这些组件以适应我们的内容。

CSS 正在改变

当容器查询的争论还在继续时,CSS 已经有了许多进步,改变了我们思考设计的方式。用像素测量固定宽度元素和使用浮动 div 元素拼凑布局的日子早已一去不复返,与表格布局一起成为历史。Flexbox 和 CSS Grid 彻底改变了网页的布局。我们现在可以创建在空间不足时换行到新行的元素,而不是在设备改变时。

1
2
3
4
5
.wrapper {
  display: grid;
  grid-template-columns: repeat(auto-fit, 450px);
  gap: 10px;
}

repeat() 函数与 auto-fitauto-fill 配对,允许我们指定每列应使用多少空间,同时由浏览器决定何时将列溢出到新行。类似的事情可以用 Flexbox 实现,因为元素可以换行多行并“伸缩”以填充可用空间。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
.wrapper {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
}

.child {
  flex-basis: 32%;
  margin-bottom: 20px;
}

这一切的最大好处是你不需要将元素包裹在容器行中。没有行,内容不会以相同的方式与页面标记绑定,允许删除或添加内容而无需额外的开发。

在创建允许内容演进的设计方面,这是一个巨大的进步,但灵活设计的真正改变者是 CSS Subgrid。

还记得那些精心制作完美对齐界面的日子吗?客户几乎一获得 CMS 访问权限就添加了一个难以置信的长标题,如下图所示?

Subgrid 允许元素响应自身内容和兄弟元素内容的调整,帮助我们创建更能适应变化的设计。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
.wrapper {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  grid-template-rows: auto 1fr auto;
  gap: 10px;
}

.sub-grid {
  display: grid;
  grid-row: span 3;
  grid-template-rows: subgrid; /* 将行设置为父网格 */
}

CSS Grid 允许我们分离布局和内容,从而实现灵活的设计。同时,Subgrid 允许我们创建可以适应变形内容的设计。在撰写本文时,Subgrid 仅在 Firefox 中支持,但上述代码可以通过 @supports 特性查询实现。

内在布局

我不能不提到内在布局,这是 Jen Simmons 创造的术语,用于描述用于创建响应可用空间的布局的新旧 CSS 特性的混合。

响应式布局使用百分比来创建灵活的列。另一方面,内在布局使用 fr 单位来创建灵活的列,这些列永远不会缩小到使内容无法辨认的程度。

fr 单位是一种方式,可以说我希望你以这种方式分配额外空间,但……永远不要让它比里面的内容小。
——Jen Simmons,《设计内在布局》

内在布局还可以利用固定和灵活单位的混合,让内容决定它占用的空间。

内在设计的突出之处在于,它不仅创建了能够经受未来设备考验的设计,而且有助于在不失去灵活性的情况下扩展设计。组件和模式可以被提取和重用,而不需要具有与先前实现相同的断点或相同数量的内容。

我们现在可以创建适应其拥有的空间、其中的内容以及周围内容的设计。通过内在方法,我们可以构建响应式组件,而不依赖于容器查询。

另一个 2010 时刻?

在我看来,这种内在方法应该和十年前的响应式网页设计一样具有开创性。对我来说,这是另一个“一切改变”的时刻。

但它似乎没有移动得那么快;我还没有像响应式设计那样有那种改变职业生涯的时刻,尽管有广泛分享和精彩的演讲引起了我的注意。

一个原因可能是我现在在一个大型组织工作,这与我在 2010 年担任的设计机构角色截然不同。在我的机构时代,每个新项目都是一张白纸,一个尝试新事物的机会。如今,项目使用现有的工具和框架,并且通常是对具有现有代码库的现有网站的改进。

另一个可能是我现在对变化准备得更充分。2010 年,我对设计整体还是新手;这种转变令人恐惧,需要大量学习。而且,内在方法并不完全是全新的;它是关于以不同的方式使用现有技能和现有 CSS 知识。

你不能用框架解决内容问题

内在设计采用稍慢的另一个原因可能是缺乏可用的快速修复框架解决方案来启动变革。

十年前,响应式网格系统无处不在。使用像 Bootstrap 或 Skeleton 这样的框架,你手头就有一个响应式设计模板。

内在设计和框架并不那么契合,因为拥有一系列单位的好处在于创建布局模板时是一种障碍。内在设计的美在于结合不同的单位并尝试技术以为你的内容获得最佳效果。

然后是设计工具。我们可能都在职业生涯的某个时刻使用过用于桌面、平板和移动设备的 Photoshop 模板来放入设计并展示网站在所有三个阶段的外观。

你现在怎么做,每个组件响应内容,布局在需要时灵活调整?这种类型的设计必须在浏览器中发生,我个人非常喜欢这一点。

关于“设计师是否应该编码”的争论是另一个已经持续多年的问题。在设计数字产品时,我们至少应该为内容的最佳和最坏情况设计。在基于图形的软件包中这样做远非理想。在代码中,我们可以添加更长的句子、更多的单选按钮和额外的标签,并实时观察设计如何适应。它仍然有效吗?设计是否过于依赖当前内容?

就个人而言,我期待内在设计成为设计标准的那一天,当设计组件可以真正灵活地适应其空间和内容,而不依赖于设备或容器尺寸。

内容优先

内容不是恒定的。毕竟,为未知或意外设计,我们需要考虑内容变化,就像我们之前的 Subgrid 卡片示例那样,允许卡片响应自身内容和兄弟元素内容的调整。

幸运的是,CSS 不仅仅是布局,许多属性和值可以帮助我们优先考虑内容。Subgrid 和伪元素如 ::first-line::first-letter 有助于将设计与标记分离,这样我们就可以创建允许变化的设计。

而不是像这样的旧标记黑客——

1
2
3
<p>
  <span class="first-line">第一行文本具有不同的样式</span>...
</p>

——我们可以根据内容出现的位置来定位内容。

1
2
3
4
5
6
7
.element::first-line {
  font-size: 1.4em;
}

.element::first-letter {
  color: red;
}

对 CSS 更大的补充包括逻辑属性,它改变了我们使用逻辑尺寸(开始和结束)而不是物理尺寸(左和右)构建设计的方式,CSS Grid 也通过 min()max()clamp() 等函数做到这一点。

这种灵活性允许根据内容进行方向性改变,这是我们需要以多种语言呈现内容时的常见要求。过去,这通常通过 Sass mixins 实现,但通常仅限于从左到右切换到从右到左的方向。

在 Sass 版本中,需要设置方向变量。

1
2
3
4
5
$direction: rtl;
$opposite-direction: ltr;

$start-direction: right;
$end-direction: left;

这些变量可以用作值——

1
2
3
4
body {
  direction: $direction;
  text-align: $start-direction;
}

——或作为属性。

1
2
margin-#{$end-direction}: 10px;
padding-#{$start-direction}: 10px;

然而,现在我们有了原生逻辑属性,消除了对 Sass(或类似工具)和预规划的依赖,这些预规划需要在代码库中全程使用变量。这些属性也开始打破设计与严格物理尺寸之间的紧密耦合,为语言和方向的变化创造更多灵活性。

1
2
margin-block-end: 10px;
padding-block-start: 10px;

还有像 text-align 这样的属性的原生开始和结束值,这意味着我们可以用 text-align: start 替换 text-align: right

像前面的例子一样,这些属性有助于构建不受一种语言约束的设计;设计将反映内容的需求。

固定和流动

我们简要介绍了在内在布局中将固定宽度与流动宽度结合的力量。min()max() 函数是类似的概念,允许你指定一个固定值和一个灵活的替代方案。

对于 min(),这意味着设置一个流动的最小值和一个最大的固定值。

1
2
3
.element {
  width: min(50%, 300px);
}

上图中的元素将是其容器的 50%,只要元素的宽度不超过 300px。

对于 max(),我们可以设置一个灵活的最大值和一个最小的固定值。

1
2
3
.element {
  width: max(50%, 300px);
}

现在,元素将是其容器的 50%,只要元素的宽度至少为 300px。这意味着我们可以设置限制,但允许内容对可用空间做出反应。

clamp() 函数通过允许我们使用第三个参数设置一个首选值来构建于此。现在,我们可以允许元素在需要时收缩或增长,而不会达到无法使用的程度。

1
2
3
.element {
  width: clamp(300px, 50%, 600px);
}

这次,元素的宽度将是其容器的 50%(首选值),但永远不会小于 300px,也永远不会大于 600px。

通过这些技术,我们有了一个内容优先的响应式设计方法。我们可以将内容与标记分离,这意味着用户所做的更改不会影响设计。我们可以通过规划语言或方向的意外变化来开始未来验证设计。我们可以通过设置期望的尺寸和灵活的替代方案来增加灵活性,允许更多或更少的内容正确显示。

情境优先

得益于我们到目前为止讨论的内容,我们可以通过改变我们的方法来覆盖设备灵活性,围绕内容和空间进行设计,而不是迎合设备。但是 Jeffrey Zeldman 引用的最后一点呢,“……你未曾想象的情境”?

为坐在台式电脑前的人设计与为在拥挤的街道上在耀眼的阳光下使用手机移动的人设计是非常不同的事情。情境和环境很难规划或预测,因为它们会随着人们对自己独特的挑战和任务的反应而变化。

这就是为什么选择如此重要。一刀切永远不适用,所以我们需要为多种场景设计,为所有用户创造平等的体验。

幸运的是,我们可以做很多事情来提供选择。

负责任的设计

“世界上有些地方移动数据贵得令人望而却步,宽带基础设施很少或根本没有。”
“我在 50 MB 预算下使用了一天网络”
Chris Ashton

我们做出的最大假设之一是,与我们的设计交互的人拥有良好的 wifi 连接和宽屏显示器。但在现实世界中,我们的用户可能是通勤者,在火车或其他交通工具上旅行,使用较小的移动设备,可能会遇到连接中断的情况。没有什么比无法加载的网页更令人沮丧的了,但我们可以帮助用户使用更少的数据或处理零星连接。

srcset 属性允许浏览器决定提供哪个图像。这意味着我们可以创建更小的“裁剪”图像在移动设备上显示,从而使用更少的带宽和数据。

1
2
3
4
5
6
<img 
  src="image-file.jpg"
  srcset="large.jpg 1024w,
          medium.jpg 640w,
          small.jpg 320w"
  alt="图像替代文本" />

preload 属性也可以帮助我们思考媒体如何以及何时下载。它可以用来告诉浏览器任何需要高优先级下载的关键资源,提高感知性能和用户体验。

1
<link rel="stylesheet
comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计