响应式设计

给所有用户提供同一份 HTML 和 CSS。通过使用几个关键技术,根据用户浏览器视口的大小(或者屏幕分辨率)让内容有不一样的渲染结果。这种方式不需要分别维护两个网站。只需要创建一个网站,就可以在智能手机、平板,或者其他任何设备上运行。网页设计师 Ethan Marcotte 称这种方式为响应式设计(responsive design)。

响应式设计的三大原则如下:

  1. 移动优先。这意味着在实现桌面布局之前先构建移动版的布局。
  2. @media 规则。使用这个样式规则,可以为不同大小的视口定制样式。用这一语法,通常叫作媒体查询(media queries),写的样式只在特定条件下才会生效。
  3. 流式布局。这种方式允许容器根据视口宽度缩放尺寸。

# 移动优先

开发移动版网页有很多限制:屏幕空间受限、网络更慢。用户跟网页交互的方式也不一样:可以打字,但是用着很别扭,不能将鼠标移动到元素上触发效果等。如果一开始就设计一个包含全部交互的网站,然后再根据移动设备的限制来制约网站的功能,那么一般会以失败告终。

而移动优先的方式则会让你设计网站的时候就一直想着这些限制。一旦移动版的体验做好了(或者设计好了),就可以用“渐进增强”(progressive enhancement)的方式为大屏用户增加体验。

移动端布局一般是很朴素的设计。除了前面提到的交互菜单,移动版设计主要关注的是内容。在大屏上,可以把页面的大块区域拿来做头部、主图、和菜单。然而在移动设备上,用户通常有更明确的目标。

移动版设计就是内容的设计。换句话说,我们希望最重要的内容先出现在 HTML 里。这一点恰好跟可访问性的关注点不谋而合:一个屏幕阅读器优先读到“重要的内容”,或者用户使用键盘浏览时先获取到文章里的链接,然后才是侧边栏里的链接。

话虽如此,这也不是一条铁律。

做响应式设计时,一定要确保 HTML 包含了各种屏幕尺寸所需的全部内容。你可以对每个屏幕尺寸应用不同的 CSS,但是它们必须共享同一份 HTML。

虽然要先给移动端写布局,但是心里装着整体的设计,才能帮助我们在实现过程中做出合适的决定。

断点——一个特殊的临界值。屏幕尺寸达到这个值时,网页的样式会发生改变,以便给当前屏幕尺寸提供最佳的布局。

因为要先实现移动版设计,所以更应该了解在更大的视口下网页长什么样,这样才能在一开始就写出合适的 HTML 结构。

https://codepen.io/cellinlab/pen/eYyLvvQ

# 创建移动版的菜单

不管用什么语言写代码都是一个迭代过程,CSS也不例外。有时候需要反复调试HTML里的代码才能实现。

矛盾的汉堡包菜单:汉堡包菜单最近几年很流行。它解决了在小屏幕里显示更多内容的问题,但是也有弊端。将重要元素(比如主要的导航菜单)隐藏起来会减少用户跟它们交互的机会。

https://codepen.io/cellinlab/pen/wvpEJjp

当设计移动触屏设备的时候,确保所有的关键动作元素都足够大,能够用一个手指轻松点击。千万不要让用户放大页面,才能点中一个小小的按钮或者链接。

# 给视口添加 meta 标签

视口的meta标签。这个 HTML 标签告诉移动设备,你已经特意将网页适配了小屏设备。如果不加这个标签,移动浏览器会假定网页不是响应式的,并且会尝试模拟桌面浏览器,那之前的移动端设计就白做了。

代码语言:javascript
复制
<meta name="viewport" content="width=device-width, initial-scale=1.0">

meta 标签的 content 属性里包含两个选项。首先,它告诉浏览器当解析 CSS 时将设备的宽度作为假定宽度,而不是一个全屏的桌面浏览器的宽度。其次当页面加载时,它使用 initial-scale 将缩放比设置为 100%

此外 content 属性还有第三个选项 user-scalable=no ,阻止用户在移动设备上用两个手指缩放。通常这个设置在实践中并不友好,不推荐使用。当链接太小不好点击,或者用户想要把某个图片看得更清楚时,这个设置会阻止他们缩放页面。

# 媒体查询

媒体查询(media queries)允许某些样式只在页面满足特定条件时才生效。这样就可以根据屏幕大小定制样式。可以针对小屏设备定义一套样式,针对中等屏幕设备定义另一套样式,针对大屏设备再定义一套样式,这样就可以让页面的内容拥有多种布局。

媒体查询使用@media规则选择满足特定条件的设备。

代码语言:javascript
复制
/**
  * 只有当设备的视口宽度大于等于 560px 的时候,才会给标题设置 2.25rem 的字号。
  * 如果视口宽度小于 560px,那么里面的所有规则都会被忽略。
  */
@media (min-width: 560px) {
  .title > h1 {
    font-size: 2.5rem;
  }
}

在最外层的大括号内可以定义任意的样式规则。@media规则会进行条件检查,只有满足所有的条件时,才会将这些样式应用到页面上。

媒体查询里面的规则仍然遵循常规的层叠顺序。它们可以覆盖媒体查询外部的样式规则(根据选择器的优先级或者源码顺序,同理,也可能被其他样式覆盖。媒体查询本身不会影响到它里面选择器的优先级。

在媒体查询断点中推荐使用 em 单位。在各大主流浏览器中,当用户缩放页面或者改变默认的字号时,只有 em 单位表现一致。以 px 或者 rem 单位为断点在 Safari 浏览器里不太可靠。同时当用户默认字号改变的时候,em 还能相应地缩放,因此它更适合当断点。

在媒体查询里更适合用 emem 是基于浏览器默认字号的(通常是 16px)。

https://codepen.io/cellinlab/pen/GRyXmjx

通过缩放浏览器窗口就能测试标题样式。当窗口很窄的时候,标题是适应移动端的小字号。慢慢放大浏览器窗口,字号会平滑地改变,因为网页被设置了响应式(calc())字号。只要网页宽度达到 35em(或者 560px),标题的字号马上就会变成 2.25rem。

# 媒体查询的类型

可以进一步将两个条件用 and 关键字联合起来组成一个媒体查询。这种联合媒体查询只在设备同时满足这两个条件时才生效。

代码语言:javascript
复制
/* 查询匹配大于 20em 小于 35em 的视口 */
@media (min-width: 20em) and (max-width: 35em) {}

如果设备只需要满足多个条件之一,可以用逗号分隔。

代码语言:javascript
复制
/* 查询匹配小于等于 20em 的视口,以及大于等于 35em 的视口 */
@media (min-width: 20em), (max-width: 35em) {}
# min-width 和 max-width 等

min-width 匹配视口大于特定宽度的设备,max-width 匹配视口小于特定宽度的设备。它们被统称为媒体特征(media feature)。

min-widthmax-width 是目前用得最广泛的媒体特征,但还有一些别的媒体特征:

  • (min-height: 20em)——匹配高度大于等于20em的视口
  • (max-height: 20em)——匹配高度小于等于20em的视口
  • (orientation: landscape)——匹配宽度大于高度的视口
  • (orientation: portrait)——匹配高度大于宽度的视口
  • (min-resolution: 2dppx)——匹配屏幕分辨率大于等于 2dppx(dppx 指每个 CSS 像素里包含的物理像素点数)的设备,比如视网膜屏幕
  • (max-resolution: 2dppx)——匹配屏幕分辨率小于等于 2dppx 的设备

更多媒体特征列表, @media (opens new window)

媒体查询还可以放在标签中。在网页里加入<linkrel="stylesheet" media="(min-width: 45em)"href="large-screen.css" />,只有当 min-width 媒体查询条件满足的时候才会将 large-screen.css 文件的样式应用到页面。然而不管视口宽度如何,样式表都会被下载。这种方式只是为了更好地组织代码,并不会节省网络流量。

# 媒体类型

常见的两种媒体类型是 screen 和 print。使用 print 媒体查询可以控制打印时的网页布局,这样就能在打印时去掉背景图(节省墨水),隐藏不必要的导航栏。当用户打印网页时,他们通常只想打印主体内容。

针对打印样式,使用 @media print 查询语句。不需要像 min-width 或者其他媒体特征那样加小括号。同理,针对屏幕样式,使用 @media screen

开发 CSS 的时候,通常在事后才会处理打印样式,而且只在需要的时候才会去考虑,但还是有必要思考用户是否想要打印网页的。为了帮助用户打印网页,需要采取一些通用步骤。

大多数情况下,需要将基础打印样式放在 @media print {...} 媒体查询内。使用 display: none 隐藏不重要的内容,比如导航菜单和页脚。还可以将整体的字体颜色设置成黑色,去掉文字后面的背景图片和背景色。

代码语言:javascript
复制
@media print {
  * {
    color: black !important;
    background: none !important;
  }
}

# 给网页添加断点

通常来说,移动优先的开发方式意味着最常用的媒体查询类型应该是 min-width。在任何媒体查询之前,最先写的是移动端样式,然后设置越来越大的断点。

代码语言:javascript
复制
/* 移动端样式,对所有的断点都生效 */
.title {}
/* 中等屏幕的断点:覆盖对应的移动端样式 */
@media (min-width: 35em) {
  .title {}
}
/* 大屏幕的断点:覆盖对应的小屏幕和中等屏幕断点的样式 */
@media (min-width: 50em) {
  .title {}
}

最优先的是移动端样式,因为它们不在媒体查询里,所以这些样式对所有断点都有效。然后是针对中等屏幕的媒体查询,其中的规则基于移动端样式构建并且会覆盖移动端样式。最后是针对大屏幕的媒体查询,在这里添加网页最后的布局。

有的设计可能只需要一个断点,有的设计可能需要多个断点。对网页上有很多元素来讲,无须给每个断点都添加样式,因为在小屏幕或者中等屏幕的断点下添加的样式规则在大屏幕的断点下也完全有效。

有时候移动端的样式可能很复杂,在较大的断点里面需要花费较大篇幅去覆盖样式。此时需要将这些样式放在 max-width 媒体查询中,这样就只对较小的断点生效,但是用太多的 max-width 媒体查询也很有可能是没有遵循移动优先原则所致。max-width 是用来排除某些规则的方式,而不是一个常规手段。

https://codepen.io/cellinlab/pen/OJzojKv

用 Flexbox 处理列表项是一个很棒的方法,它能够让列表项增长到填满可用空间。

# 添加响应式的列

许多响应式设计遵循这种方法:当设计要求元素并排摆放时,只在大屏上将它们摆放在一行。在小屏下,允许每个元素单独一行,填满屏幕宽度。这种方法适用于列、媒体对象,以及任意在小屏下容易拥挤的元素。

https://codepen.io/cellinlab/pen/xxpaXzd

响应式设计中的列非常灵活多变,比如一宽一窄的列、等宽的列、两列、三列。

更多响应式模式可以参考 this is responsive (opens new window)。

# 断点的选择

不要总想着设备。市面上有成百上千中设备和屏幕分辨率,无法逐一测试。相反,应该选择适合设计的断点,这样不管在什么设备上,都能有很好的表现。

# 流式布局

流式布局,有时被称作液体布局(liquid layout),指的是使用的容器随视口宽度而变化。它跟固定布局相反,固定布局的列都是用 px 或者 em 单位定义。固定容器(比如,设定了 width: 800px 的元素)在小屏上会超出视口范围,导致需要水平滚动条,而流式容器会自动缩小以适应视口。

在流式布局中,主页面容器通常不会有明确宽度,也不会给百分比宽度,但可能会设置左右内边距,或者设置左右外边距为 auto,让其与视口边缘之间产生留白。也就是说容器可能比视口略窄,但永远不会比视口宽。

在主容器中,任何列都用百分比来定义宽度(比如,主列宽 70%,侧边栏宽 30%)。这样无论屏幕宽度是多少都能放得下主容器。用 Flexbox 布局也可以,设置弹性元素的 flex-grow 和 flex-shrink(更重要),让元素能够始终填满屏幕。要习惯将容器宽度设置为百分比,而不是任何固定的值。

网页默认就是响应式的。没添加 CSS 的时候,块级元素不会比视口宽,行内元素会折行,从而避免出现水平滚动条。加上 CSS 样式后,就需要来维护网页的响应式特性了。这个道理说着容易做着难,而意识到每次都是从一个好的默认状态开始,有助于我们更好地实现响应式布局。

# 给大视口添加样式

https://codepen.io/cellinlab/pen/bGaxYNb

# 处理表格

在移动设备的流式布局里,表格的问题特别多。如果表格的列太多,很容易超过屏幕宽度。

如果可以的话,建议在移动设备上用别的方式组织数据。比如将每一行数据单独用一块区域展示,让每块区域顺序叠放,或者用更适合小屏的可视化图形或者图表展示。但是,有时候就是需要用表格。

在移动设备上实现表格的响应式布局

代码语言:javascript
复制
table {
  width: 100%;
}
@media (max-width: 30em) {
  /* 让表格所有的元素都显示为块级 */
  table, thead, tbody, tr, th, td {
    display: block;
  }
  /* 将表头移到屏幕外 */
  thead tr {
    position: absolute;
    top: -9999px;
    left: -9999px;
  }
  .tr {
    margin-bottom: 1em;
  }
}

# 响应式图片

在响应式设计中,图片需要特别关注。不仅要让图片适应屏幕,还要考虑移动端用户的带宽限制。图片通常是网页上最大的资源。

首先要保证图片充分压缩。还要避免不必要的高分辨率图片,而是否必要则取决于视口大小。也没有必要为小屏幕提供大图,因为大图最终会被缩小。

# 不同视口大小使用不同的图片

响应式图片的最佳实践是为一个图片创建不同分辨率的副本。如果用媒体查询能够知道屏幕的大小,就不必发送过大的图片,不然浏览器为了适配图片也会将其缩小。

使用响应式技术给不同屏幕尺寸提供最合适的图片。

https://codepen.io/cellinlab/pen/LYeJOrM

# 使用 srcset 提供对应的图片

媒体查询能够解决用 CSS 加载图片的问题,但是 HTML 里的 <img> 标签怎么办呢?对于这种行内图片,有另一个重要的解决方法:srcset 属性(“source set”的缩写)。

HTML 的一个较新的特性。它可以为一个 <img> 标签指定不同的图片URL,并指定相应的分辨率。浏览器会根据自身需要决定加载哪一个图片。

代码语言:javascript
复制
<img alt="" src="coffee-beans-small.jpg"
  srcset="coffee-beans-small.jpg 560w, coffee-beans-medium.jpg 800w,coffee-beans.jpg 1280w">

现在大多数浏览器支持 srcset 。不支持的浏览器会根据 src 属性加载相应的 URL。这种方式允许针对不同的屏幕尺寸优化图片。更棒的是,浏览器会针对高分辨率的屏幕做出调整。

图片作为流式布局的一部分,请始终确保它不会超过容器的宽度。为了避免这种情况发生,一劳永逸的办法是在样式表加入规则 img { max-width: 100%; }

网页响应式设计的结构实现方式千变万化。最终这些方式都会归纳为三大原则:移动优先、媒体查询、流式布局。