Skip to content
This page has been auto-translated and may contain errors.View in English

响应式设计

docs.scrimba.com

你在笔记本电脑上构建了一个页面,看起来很不错,然后有人在手机上打开它。你精心排列的列现在被挤成了一条细缝,文本跑到了边缘外,本应没有水平滚动条的地方出现了一条。没人写过该网站的单独手机版本。只有一个页面,它必须在380像素的手机和1400像素的显示器上看起来都很好,而不需要你复制两份所有东西。这种适应性就是响应式设计,而且它的大部分是由你已经拥有的工具构建的。

响应式的含义和视口

一个**响应式**页面是一个能够改变自身形状以适应它所呈现的任何屏幕的页面。同样的HTML和同样的CSS为手机、平板电脑和桌面提供服务,布局会调整,使得没有任何东西溢出,也没有任何东西感到紧张。你没有在构建三个网站。你正在构建一个会弯曲的网站。

在任何这些工作之前,浏览器需要HTML中的一条指令。手机过去常常装作是桌面宽度并将整个页面缩小以适应,这就是为什么旧网站显示为一个小的、缩小的混乱。这一行位于你HTML的<head>中,告诉手机改为使用其真实宽度:

html
<meta name="viewport" content="width=device-width, initial-scale=1" />

没有它,你的CSS就是在为一个虚拟的宽屏幕制作样式,你精心设计的布局永远没有机会。

响应式设计意味着一个单一的代码库,它适应**视口**大小,即浏览器窗口内页面的可见区域。有一组HTML和一个样式表;改变的是这个样式表如何响应它被赋予的空间。替代方案是独立的移动和桌面网站,这会使你的维护工作加倍,这就是为什么该行业多年前就采用了响应式。

起始要求是HTML头中的视口元标签。它将浏览器布局所依据的宽度设置为设备的实际宽度,并阻止移动浏览器以假的桌面宽度呈现并缩小:

html
<meta name="viewport" content="width=device-width, initial-scale=1" />

width=device-width将布局视口与屏幕相匹配,initial-scale=1将初始缩放设置为100%。省略这一部分,再多的正确CSS也救不了布局,因为浏览器是根据错误的宽度进行测量的。

响应式设计是构建一个文档的实践,其布局适应**视口**大小的连续范围,而不是针对少数几个固定屏幕。视口是浏览器用来布局和绘制页面的区域,其宽度是每个相对单位、百分比和媒体查询最终解析的值。弄错那个宽度,所有下游的东西都在测量错误的东西。

这正是视口元标签防止的。移动浏览器在历史上区分了布局视口(CSS布局所依据的宽度)和视觉视口(屏幕上实际显示的内容),将布局视口默认设置为约980px,这样仅限桌面的遗留网站可以呈现然后缩小以适应。元标签消除了这个差距:

html
<meta name="viewport" content="width=device-width, initial-scale=1" />

width=device-width将布局视口与设备自身的CSS像素宽度相关联,initial-scale=1固定初始缩放,使两个视口开始对齐。它属于HTML头中,它是先决条件,而不是美化:没有它,浏览器会根据390px手机上的约980px来评估你的媒体查询和百分比,所以min-width: 40rem查询会在不应该时触发,你测试的布局永远不会出现。本章中的所有内容都假设该标签存在。

Juno响应式的含义和视口 响应式页面是一个能改变自身形状以适应任何屏幕(从手机到桌面)的单一页面,使用一组HTML和CSS。在你的CSS有机会工作之前,HTML头需要视口元标签,它告诉手机使用其真实宽度而不是装作是桌面。省略它,布局看起来会损坏,无论CSS有多好。
Juno响应式的含义和视口 响应式意味着一个适应视口(可见页面区域)的单一代码库,而不是独立的移动和桌面构建。HTML头中的视口元标签是起始要求:width=device-width使浏览器根据真实屏幕宽度进行布局。跳过它,你的媒体查询会根据假的桌面宽度触发,所以正确的CSS看起来仍然错误。
Juno响应式的含义和视口 每个百分比和媒体查询都根据视口宽度进行解析,所以视口元标签是先决条件而不是美化。width=device-width, initial-scale=1将布局视口与设备的真实宽度相关联,而不是遗留的980px默认值。在手机上忘记它,min-width: 40rem查询会在不应该时触发,你测试的布局永远不会呈现。

流动布局优先

直觉的做法是立即编写特殊的手机规则,但在你编写单个规则之前,许多响应性是免费的。如果你用**流动**值构建,那些随空间伸缩的值,布局已经适应了。

两个习惯承载了大部分内容。将宽度设置为百分比,使盒子占其容器的一部分而不是固定的像素数,并使用max-width使某物在小屏幕上可以缩小但在大屏幕上永远不会超过舒适的阅读大小:

css
.container {
  width: 90%;              /* 占它拥有的任何空间的90% */
  max-width: 60rem;        /* 但在大屏幕上永远不超过60rem */
  margin: 0 auto;          /* 居中 */
}

在手机上,这个容器是狭窄屏幕的90%。在显示器上,它停止在60rem处增长并居中。你没有编写手机规则;这些值进行了适应。

在媒体查询之前,用**流动**值构建布局,使其自动弯曲。相对单位和百分比让元素占其容器的一部分而不是固定大小,flexboxgrid已经在空间改变时重排它们的子元素。一个好的流动布局需要比严格布局少得多的断点,因为它首先不是在与屏幕作对。

只有在流动布局真的不再适应时才考虑媒体查询。百分比和max-width处理宽度,一个网格模式甚至不需要任何查询就能处理常见的卡片墙情况:

css
.card-grid {
  display: grid;
  gap: 1.5rem;
  /* 尽可能多的列,每个最少16rem,共享剩余空间 */
  grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
}

repeat(auto-fit, minmax(16rem, 1fr))说的是"尽可能多地容纳最少16rem的列,然后拉伸它们来填充行"。在宽屏幕上三列,平板电脑上两列,手机上一列,没有一个媒体查询。在尺寸单位中涵盖了这些值优先使用哪些相对单位。

最强大的响应式布局在编写单个媒体查询之前大多是**流动**的,因为断点是一个离散跳跃,大部分工作是连续的。百分比、max-width和内置于flexboxgrid中的内在尺寸让布局平滑地跟踪视口,然后媒体查询只处理设计真正需要改变形状的点,而不是每一个调整像素。

值得信赖的模式是自调整网格,因为它将一整组断点折叠成一个声明:

css
.card-grid {
  display: grid;
  gap: 1.5rem;
  grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
}

minmax(16rem, 1fr)给每个轨道一个16rem的下限和一个等份自由空间的上限,auto-fit创建尽可能多的轨道以适应该下限,然后展开它们以填充行。列数根据容器宽度自动确定,所以需要三个断点的布局现在不需要任何断点。值得携带的一个精度说明:auto-fit折叠空轨道使当前项目拉伸以填充行,而auto-fill保留空轨道预留,所以当你希望项目增长到剩余空间时选择auto-fit,当你希望稳定网格时选择auto-fill。见尺寸单位了解这些remfr值如何解析。

Juno流动布局优先 许多响应性在你编写任何特殊规则之前是免费的:使用百分比使盒子占其空间的一部分,使用max-width使它们在手机上收缩而不会在显示器上拉伸太宽。这两个值自己适应布局。流动优先构建,你将需要少得多的接下来的手机特定规则。
Juno流动布局优先 在构建断点之前构建流动:百分比和max-width处理宽度,flexbox和grid已经自己重排。要记住的一个网格行是repeat(auto-fit, minmax(16rem, 1fr)),它给你一个零媒体查询的响应式卡片墙。只有在流动布局真的停止工作时才考虑查询。
Juno流动布局优先 断点是离散跳跃,但调整大小是连续的,所以让百分比、max-width和内在flex和grid尺寸承载平滑部分,并为真正的形状改变保留媒体查询。repeat(auto-fit, minmax(16rem, 1fr))将几个断点折叠成一行。记住auto-fit折叠空轨道以拉伸你的项目,而auto-fill保留它们预留。

媒体查询

有时流动值不够,你需要布局在特定大小真正改变,例如在手机上垂直堆叠但在桌面上排成一行的菜单。那就是**媒体查询**的用途:一个CSS块,仅在屏幕满足你设置的条件时应用。

从小屏幕的样式开始作为你的常规CSS,然后添加一个查询来为更宽屏幕的改变分层:

css
.nav {
  display: flex;
  flex-direction: column;   /* 在小屏幕上堆叠 */
}

@media (min-width: 40rem) {
  .nav {
    flex-direction: row;    /* 有空间后排成一行 */
  }
}

@media (min-width: 40rem)内的规则仅在屏幕至少40rem宽时切换。在这之下,普通的.nav规则应用,菜单堆叠。

一个**媒体查询将一组规则包装在一个条件中,通常是宽度测试,这些规则仅在条件成立时应用。要采用的模式是移动优先**:为最小屏幕编写基础样式,完全不需要查询,然后用min-width向上分层增强。

css
.layout {
  display: grid;
  gap: 1rem;
  grid-template-columns: 1fr;      /* 基础:单列,小屏幕 */
}

@media (min-width: 40rem) {
  .layout {
    grid-template-columns: 1fr 1fr; /* 有空间后两列 */
  }
}

移动优先的意义不仅在于整洁。你的基础样式是始终应用的样式,所以使它们成为最简单、最可靠的布局意味着最小和最受限的设备默认获得最安全的体验,每个查询只会增加能力。另一种方式构建,桌面优先,带有max-width查询来剥离东西,往往会让手机视图成为一个事后思考,最后破裂。

一个**媒体查询仅当媒体条件评估为真时才应用一块规则,对于响应式布局,该条件几乎总是视口宽度测试。坚持的纪律是移动优先**:无条件基础样式描述狭窄布局,每个min-width查询都纯粹加在它们之上。

css
.layout {
  display: grid;
  gap: 1rem;
  grid-template-columns: 1fr;       /* 基础,应用于任何地方 */
}

@media (min-width: 40rem) {         /* 平板及以上 */
  .layout { grid-template-columns: 1fr 1fr; }
}

@media (min-width: 64rem) {         /* 桌面及以上 */
  .layout { grid-template-columns: 1fr 2fr 1fr; }
}

优先使用累加min-width而不是递减max-width的原因是级联。因为这些查询共享特异性,后触发的覆盖前面的,所以从小到大排列它们意味着每个更宽的断点在其应用的地方清晰地获胜。两个值得持有的精度:在条件中优先使用rem而不是px,使断点跟踪用户的字体大小,并知道宽度查询读取的是布局视口,只有在视口元标签将其固定到设备宽度时这才正确。一个min-width堆栈中间的流浪max-width查询是规则神秘地停止应用的常见原因,因为它在与排列对抗而不是扩展它。

Juno媒体查询 媒体查询是一块CSS,仅在屏幕满足一个条件(如至少40rem宽)时才切换。首先编写你的小屏幕样式作为常规CSS,然后使用@media (min-width: ...)添加更大屏幕的改变。这样手机视图就是你的起点,而不是事后贴上的补丁。
Juno媒体查询 媒体查询仅在条件(通常是宽度)成立时才应用规则。从移动优先开始:最小屏幕的基础样式,没有查询,然后用min-width向上分层,使每个查询只添加。另一种方式用max-width构建,手机视图往往会成为破损的事后思考。
Juno媒体查询 移动优先意味着狭窄布局的无条件基础样式和累加min-width查询从小到大堆叠,所以级联清晰地解决每个断点。在条件中使用rem使其跟踪用户的字体大小,并记住宽度查询读取元标签固定的布局视口。一个min-width堆栈中的单个max-width是规则悄悄停止应用的经典原因。

选择断点

一个**断点**是媒体查询启动和布局改变的宽度。诱人的方法是查找一个流行手机或平板电脑的宽度并匹配它。那就是陷阱。有数百个设备大小,它们每年都在变化,追赶它们是一场你无法赢的游戏。

更好的问题是:在什么宽度时,这个布局开始看起来不好?慢慢地加宽你的浏览器并观看。当一行文本变得太长而无法舒适阅读,或两列变得太窄时,那就是断点应该出现的地方,无论它碰巧是什么数字。

css
/* 选择断点是因为文本在这里变得太宽了,*/
/* 而不是因为手机碰巧是这个大小 */
@media (min-width: 45rem) {
  .article { max-width: 38rem; }
}

一个**断点**是你改变布局的视口宽度。通过观看你自己的内容来选择它们,而不是通过匹配设备名称。像"768px因为那是一个iPad"这样的整数感觉很有权威性,但是一个陷阱:设备景观不断变化,一个为去年的流行大小调整的布局在今年的会损坏。

让内容决定。调整窗口大小并在设计真正受压的宽度处添加断点,一行变得太长,卡片变得太拥挤,导航用尽空间。在整个网站中你通常只需要两三个:

css
/* 内容驱动:这行约75个字符并变得难以阅读 */
@media (min-width: 42rem) {
  .prose { max-width: 65ch; }   /* 有空间后限制行长 */
}

/* 内容驱动:侧边栏布局仅在这里值得占用空间 */
@media (min-width: 60rem) {
  .page { grid-template-columns: 16rem 1fr; }
}

一个**断点**是设计改变形状的地方,选择它的耐久方式是让内容设置它而不是设备规格表。匹配命名设备宽度是脆弱的,原因是结构性的:屏幕大小的种群很大并且每年都在漂移,所以任何关键到特定设备尺寸的布局是关键到一个移动目标的,并按计划腐烂。一个由内容证明的断点,"行长在这里超过了可读行长",无论下一个硬件何时发货都保持证明。

实际上这意味着调整大小直到布局受压并在那里放置断点,这意味着大多数网站只需要少数几个:

css
/* 在行长超过可读长度时限制行长,约65个字符 */
@media (min-width: 42rem) {
  .prose { max-width: 65ch; }
}

/* 仅在主列能够提供宽度时引入侧边栏 */
@media (min-width: 60rem) {
  .page { grid-template-columns: 16rem 1fr; }
}

高级的做法是使用clamp()处理流动类型和间距,这样你根本需要更少的断点。clamp(min, preferred, max)选择一个随视口缩放但在两端都有界的值,所以标题和间距平滑地增长而不是在每个查询处跳跃:

css
h1 {
  /* 永不低于1.75rem,永不高于3rem,之间流动 */
  font-size: clamp(1.75rem, 4vw + 1rem, 3rem);
}

这涵盖了大多数排版缩放而不需要任何断点;见尺寸单位了解vw项如何解析以及排版了解类型缩放方面。三个相关工具来完成这个。容器查询根据组件自身容器的宽度而不是视口来设置组件样式,所以卡片可以对它所在的列而不是整个页面做出反应,这是视口基础媒体查询的基于组件的对应物。prefers-reduced-motion查询让你为要求系统减少运动的读者切断动画,prefers-color-scheme让你尊重系统浅色或深色偏好,两者都写作媒体查询:

css
@media (prefers-reduced-motion: reduce) {
  * { animation: none; transition: none; }  /* 尊重操作系统设置 */
}

@media (prefers-color-scheme: dark) {
  body { background: #111; color: #eee; }
}

对于图像具体来说,响应性进入HTMLsrcset属性让浏览器为每个屏幕选择一个适当大小的文件,HTML响应式图像章节涵盖了这一点而不是CSS。

Juno选择断点 断点是你布局改变的宽度,陷阱是选择它来匹配特定手机或平板电脑。有太多设备大小要追赶,它们每年都在变化。慢慢地加宽你的浏览器并在布局真正开始看起来不好的地方添加断点,无论那个数字碰巧是什么。
Juno选择断点 让内容选择你的断点,而不是设备名称:一行变得太长或卡片变得太拥挤是真正的信号,圆形设备宽度如768px是一个移动目标会腐烂。调整大小直到设计受压,并在那里放置断点。大多数网站在整个东西中只需要两三个。
Juno选择断点 内容驱动的断点存活下来因为屏幕种群每年漂移,所以任何关键到设备尺寸的东西按计划腐烂。使用clamp()处理流动类型和间距需要更少的断点,为基于组件而不是视口的样式记住容器查询,并尊重prefers-reduced-motionprefers-color-scheme。对于图像,响应性位于HTMLsrcset中,而不是CSS。