1. 先搞清楚19~21课到底在讲什么
1.1 为什么这三节课是“分水岭”
很多人学 HTML & CSS 学到一半就放弃了,不是教材太差,而是布局一直没摸到门道。前面十几节课无非是标签、选择器、盒子模型、浮动,这些内容学完单独都能看懂,一旦要写一个完整页面就手忙脚乱。白纸编程这套课程的 19~21 课,恰恰就是把“静态知识”和“真实页面”之间的沟给填平了。
第19课讲 Flex 弹性布局,解决的是“一排元素怎么排列”的问题;第20课讲 Grid 网格布局,解决的是“整个页面怎么划分区域”的问题;第21课讲响应式设计,解决的是“不同屏幕尺寸下页面怎么自适应”的问题。这三课连在一起看,其实就是一套完整的前端页面布局解决方案,学完之后基本能脱离“照着教程敲”的阶段,开始独立写页面了。
我记得自己刚开始学布局时的状态:每个属性都认识,但真要排版的时候只会用浮动。做完这三课的练习后最大的改变是——心里有了一张“布局地图”。遇到导航栏就想到 Flex,遇到页面骨架就想到 Grid,遇到手机端适配就想到媒体查询和 rem。这种条件反射式的思路,正是这三课真正的价值。
1.2 课程之间的递进关系
用一个生活化类比来解释这三课的关系:Flex 像是一条绳子,把一串珠子按规律串起来;Grid 像是一张网格纸,规划出每个格子放什么东西;响应式设计则像是一个智能箱子,东西还是那些东西,但会随着外界尺寸自动调整摆放方式。
从技术体系上看,这个递进关系也很清晰:
| 课程 | 核心关键词 | 解决的核心问题 | 典型应用场景 |
|---|---|---|---|
| 第19课 | Flex、主轴、交叉轴、flex 属性 | 一维方向上的排列与对齐 | 导航栏、按钮组、卡片行 |
| 第20课 | Grid、网格轨道、区域、间距 | 二维平面上的区域划分 | 页面骨架、商品列表、后台管理 |
| 第21课 | 媒体查询、rem、视口 | 不同屏幕尺寸下的自适应 | 手机端适配、多端页面 |
如果你按这个主线去学习,会明显感觉到课程编排是花了心思的:先用 Flex 解决最常见的“一条线”问题,再用 Grid 解决“整张网”的问题,最后用响应式设计把所有布局放到真实设备上验证。我的建议是,学完第19课和第20课之后,一定不要急着往后赶,先动手做两个小页面,把两种布局的适用场景吃透,后面第21课的综合实战才不会卡住。
2. 第19课:Flex 布局怎么从“手写定位”变成“一套规则”
2.1 两条轴线,先把方向定死
Flex 布局最核心的概念是“主轴”和“交叉轴”。所有子项的排列行为,都是围绕这两条轴展开的。默认情况下,主轴是水平方向,交叉轴是垂直方向,子项从左往右排;一旦你设置了flex-direction: column,主轴就变成了垂直方向,子项变成从上往下排。
最容易犯的错是搞混justify-content和align-items。我自己的记忆方法是:justify-content管的是主轴方向,align-items管的是交叉轴方向。这句话看着简单,但很多写了半年页面的人也会在这两个属性上翻车。比如你设了align-items: center却发现元素没居中,大概率是弄错了主轴方向。
看一个实际例子,这是课程里最常见的导航栏结构:
<nav class="navbar"> <div class="logo">Logo</div> <ul class="menu"> <li><a href="#">首页</a></li> <li><a href="#">课程</a></li> <li><a href="#">关于</a></li> </ul> <a href="#" class="login-btn">登录</a> </nav>对应的核心样式:
.navbar { display: flex; align-items: center; justify-content: space-between; } .menu { display: flex; gap: 16px; list-style: none; }.navbar里三个子项分别是 logo、菜单、登录按钮,justify-content: space-between把它们推到两边,align-items: center让它们在垂直方向居中。菜单内部再用一次 Flex,配合gap: 16px控制菜单项的间距。两行代码,一个完整导航栏就出来了,换浮动写至少要七八行。
这里有个细节值得注意:gap属性在 Flex 布局里同样可用,不需要用margin去一个个加间距。课程里会讲margin-right的旧写法,但实际开发中我强烈建议直接用 gap,省心且不会出现最后一个元素多余间距的问题。
2.2 子项尺寸分配:flex-grow、flex-shrink、flex-basis
Flex 布局真正厉害的地方在于子项尺寸的自动分配。很多新手只学了flex: 1,却不知道这个简写背后的三个属性分别代表什么。把它们拆开看,你才能真正理解 Flex 的伸缩机制。
flex-grow决定空间富余时子项如何放大,默认值为 0,表示不放大;flex-shrink决定空间不足时子项如何缩小,默认值为 1,表示可以缩小;flex-basis决定子项在主轴方向上的初始尺寸,默认值为 auto,表示根据内容决定。
举个课程里的经典例子,三张等宽卡片:
.card { flex: 1; /* 等价于 */ flex-grow: 1; flex-shrink: 1; flex-basis: 0%; }注意flex: 1的flex-basis是0%,不是auto。这两者的区别非常关键:basis: 0%意味着所有子项的初始宽度都是 0,然后在空间分配上完全均等;basis: auto则会先按内容尺寸计算,再在剩余空间上分配。如果你想让两个卡片真正等宽,用flex: 1是准确的,但如果你希望卡片“内容多就宽一点,内容少就窄一点”,就用flex: auto(即flex: 0 1 auto)。
课程里还提到一个很实用的比例分配场景:左侧固定侧边栏,右侧内容区占满剩余空间。
.container { display: flex; } .sidebar { width: 240px; flex-shrink: 0; } .main { flex: 1; min-width: 0; }这里给.sidebar加了flex-shrink: 0,防止空间不够时侧边栏被压缩;.main用flex: 1占满剩余空间。min-width: 0是我踩过坑后才学会的细节:Flex 子项的默认min-width是 auto,如果内容里有长文本或图片,子项会强行撑开,导致布局溢出。加上min-width: 0才能允许子项收缩到比内容更窄。
2.3 容易被忽略的几个 Flex 实用技巧
课程主线上没有展开讲,但实际写页面时几乎必用的几个技巧,值得记录一下。
第一个是margin: auto在 Flex 布局中的“吸边”效果。在普通文档流里,margin: auto只能水平居中元素;但在 Flex 容器中,设置margin-left: auto会把元素推到主轴末端。比如导航栏右侧只有一个按钮,你不知道左右两侧如何分布,直接给按钮margin-left: auto就能实现和justify-content: space-between一样的效果。
第二个是align-self单独控制交叉轴对齐方式。父容器设置了align-items: center,但某一个子项想固定在顶部,可以直接在这个子项上写align-self: flex-start。这个属性很适合做卡片列表里“部分卡片顶部对齐”的场景。
第三个是嵌套 Flex 的层级问题。很多新手写页面时把所有元素都放在一个 Flex 容器里,试图用一层 Flex 搞定所有排列,结果越写越乱。正确的做法是:一个 Flex 容器只负责直接子项的排列,容器内部的子项如果再需要排列,就再套一层 Flex。换句话说,Flex 不是全局布局工具,而是局部排列工具。
3. 第20课:Grid 网格,真正适合“二维布局”的工具
3.1 从“一行”到“整面”:Grid 的核心思路
Flex 擅长处理“一条线”上的布局,但遇到整个页面的骨骼结构就显得力不从心。比如有一个后台管理页面,顶部是导航栏,左侧是侧边栏,右侧是内容区,底部是页脚。这种结构用 Flex 嵌套虽然也能写,但代码层级会很深,后期维护非常痛苦。Grid 的出现就是来解决这个问题的:它把容器划分为行和列的网格,元素可以直接放进对应的网格区域里。
第20课的第一个重点是grid-template-columns和grid-template-rows,这两个属性定义了网格的轨道。看一个简单示例:
.layout { display: grid; grid-template-columns: 240px 1fr; grid-template-rows: auto 1fr auto; min-height: 100vh; }这段代码创建了一个两列三行的网格:第一列固定 240px,第二列自适应剩余宽度;行高分别是自动、占满剩余空间、自动。用这种结构可以撑起一个最常见的后台页面骨架。相比 Flex 的层层嵌套,Grid 的代码量少了一个量级,而且每一块的职责非常清晰。
课程里还重点解释了一个单位:fr。它是“fraction”的缩写,表示“剩余可用空间的份额”。1fr等于一份,2fr等于两份。这个单位比百分比好用得多,因为百分比遇到gap和固定尺寸轨道时容易溢出,fr会自动在扣除固定轨道和间距之后分配剩余空间,不会算错。
3.2 区域划分与定位:grid-template-areas 实战
课程里最让我惊艳的是grid-template-areas这个写法,它用一种近乎“画图”的方式来定义页面布局。你先把页面的区域用字符串画出来,再用grid-area属性把元素放进对应位置。
示例,第20课里的页面骨架:
<div class="page"> <header class="header">顶部导航</header> <aside class="sidebar">侧边栏</aside> <main class="main">主体内容</main> <footer class="footer">底部信息</footer> </div>对应的 CSS:
.page { display: grid; grid-template-columns: 240px 1fr; grid-template-rows: auto 1fr auto; grid-template-areas: "header header" "sidebar main" "footer footer"; min-height: 100vh; } .header { grid-area: header; } .sidebar { grid-area: sidebar; } .main { grid-area: main; } .footer { grid-area: footer; }grid-template-areas里的字符串就是一张直观的地图:第一行是两个格子组成的 header,第二行左边 sidebar 右边 main,第三行 footer 横跨两列。你根本不需要计算元素在第几行第几列,只需要维护这张“地图”就行。改布局的时候也只需要改字符串,元素本身不用动。
这个写法唯一的坑是:每一行的字符串必须用相同的列数,也就是说每个“单词”代表一个网格单元,第一行写了两个单词,后面的每一行都必须写两个。否则整个属性会失效,而且浏览器不会报错,只在开发者工具里显示解析失败。
3.3 灵活尺寸:fr、minmax 与自动填充
基础用法掌握之后,课程还介绍了两个能大幅提升生产力的高级尺寸写法。
第一个是minmax()。它给网格轨道设置一个最小值和最大值,比如minmax(240px, 1fr)表示这个轨道最少 240px,最大占满一份剩余空间。当与repeat()连用时,可以实现非常优雅的自适应卡片列表:
.card-list { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 16px; }auto-fit让浏览器自动计算能放下多少个 240px 的列,并在容器变宽时自动增加列数,变窄时自动减少列数。加这一行代码,你就能得到一个不写任何媒体查询也能自动换行排布的卡片列表。课程里要求我们记住auto-fit和auto-fill的区别:auto-fit会拉伸空轨道使其占满容器,auto-fill则保留空轨道。视觉上的直观理解就是卡片列表用 auto-fit,右侧留白少的网格用 auto-fill。
第二个是显式网格与隐式网格的区分。用grid-template-columns定义的轨道是显式轨道;如果元素被放进了一个没有定义过的行,浏览器会自动创建一行隐式轨道。隐式轨道默认高度是 auto,会让这些自动创建的行各自适应内容。如果你想控制隐式轨道的行为,可以用grid-auto-rows属性。比如希望自动出现的行高统一为 200px,就写grid-auto-rows: 200px。
4. 第21课:响应式设计,把页面从电脑搬进手机
4.1 视口与 rem,先解锁移动端浏览器
很多新手写完页面后用手机打开,发现字特别小、布局完全错乱,第一反应是 CSS 写错了。其实真正的原因是少了视口设置。移动端浏览器默认会用一个大约 980px 宽的虚拟视口来渲染页面,然后再整体缩小显示,所以你的页面看起来就像一个缩小版桌面页面,字自然就小得看不清。
解决办法是加一行 meta 标签:
<meta name="viewport" content="width=device-width, initial-scale=1.0">这行代码告诉移动端浏览器:用设备的实际宽度作为视口宽度,初始缩放比例为 1。加了它之后,你的页面在手机上才会按照真实屏幕宽度渲染。
第21课接着讲了rem单位。rem和em的区别经常把新手绕晕:em是相对于当前元素的字体大小,父子嵌套时会被层层放大;rem始终相对于根元素html的字体大小,不存在嵌套放大问题。在响应式布局中,用rem做全局字号和间距单位是非常稳妥的选择,因为只需要改变根元素的字体大小,整页所有用 rem 的地方都会跟着变。
html { font-size: 16px; } h1 { font-size: 2rem; /* 32px */ } .title { padding: 1.5rem; /* 24px */ }很多人喜欢直接写死font-size: 16px,在小屏设备上就会出现阅读困难。响应式设计更推荐的做法是给根元素的字号加一个百分比缩放,或者用clamp()函数让它随屏幕宽度动态变化。
4.2 媒体查询的最佳实践:移动优先
媒体查询是响应式设计的核心工具,写法是:
/* 默认样式:移动端 */ .card-list { display: block; } /* 屏幕宽度 ≥ 768px 时生效 */ @media (min-width: 768px) { .card-list { display: grid; grid-template-columns: repeat(2, 1fr); gap: 16px; } } /* 屏幕宽度 ≥ 1024px 时生效 */ @media (min-width: 1024px) { .card-list { grid-template-columns: repeat(4, 1fr); } }这里采用的是“移动优先”策略:先写手机端的默认样式,再用min-width媒体查询逐级增强桌面端样式。这样做的好处是,小屏设备的 CSS 是基础样式,不会因为大屏浏览器解析不了某些属性而出问题;而且移动端样式通常更简单,先写简单代码也符合思路从简到繁的过程。
我不建议用max-width做桌面优先,也就是先写大屏样式再用max-width覆盖小屏。这种写法虽然效果上也能实现,但代码量变多,而且断点一旦冲突,调试起来会非常痛苦。课程里也强调了这一点:响应式的核心不是“适配所有屏幕”,而是“让你的内容在任意宽度下都保持可读、可用”。
常见的断点可以用一组参考值,但这只是经验值,不是标准:
| 断点 | 适用场景 |
|---|---|
| 576px | 小屏手机横屏、大屏手机竖屏 |
| 768px | 平板竖屏 |
| 992px | 平板横屏、小笔记本 |
| 1200px | 桌面显示器 |
实际项目中不要死记这些数字,而是以你页面的真实内容为基准,在内容开始拥挤的时候加断点。
4.3 综合实战:把前两课的布局改造成响应式
第21课的最后一个练习,是把第19课的导航栏和第20课的页面骨架改造成响应式布局。这一步非常关键,它把三课内容串成了一条完整的线。
核心思路是:小屏设备上,侧边栏不再占两列结构的第一列,而是直接放到主体内容上方;导航栏的菜单文字变小或改用图标按钮;卡片列表从一行四列降级为一行一列。
示例代码,页面骨架的响应式改造:
.page { display: grid; grid-template-columns: 1fr; grid-template-areas: "header" "sidebar" "main" "footer"; } @media (min-width: 768px) { .page { grid-template-columns: 240px 1fr; grid-template-areas: "header header" "sidebar main" "footer footer"; } }在移动端,整个页面变成单列纵向排列,侧边栏直接出现在主要内容上方;到了平板宽度以上,才切换到两列骨架。代码改动量极小,思路非常清晰。这就是 Grid 配合媒体查询的典型用法:整体结构由 Grid 管,断点切换由媒体查询管。
同样的思路放在导航栏上,小屏时菜单项可能太挤,可以考虑把菜单的文字字号缩小,或者干脆隐藏次要菜单。但课程里提醒了一个原则:不要为了“看起来像移动端适配”而把功能藏起来。如果一个菜单在手机上是必要的,你应该重新考虑它的排布和层级,而不是直接display: none掉。
5. 我踩过的坑与常用问题排查速查表
5.1 高频问题速查表
三课学完,我在实际练习里踩过不少坑,有些是课程里提到的,更多是课程没展开但亲手敲代码才发现的。整理成一张速查表,方便直接对照排查。
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
设置了justify-content没反应 | 子项总宽度等于容器宽度,没有剩余空间可分配 | 确认容器宽度是否大于子项总宽度,或检查flex-wrap |
两个flex: 1的元素宽度却不同 | flex-basis不是 0%,或元素有最小宽度限制 | 使用flex: 1简写,必要时加min-width: 0 |
| Grid 子项被自动拉伸到整行 | 没指定轨道尺寸,或子项属性设置错误 | 用grid-template-columns/grid-auto-rows显式定义 |
设置了gap没生效 | 浏览器版本过低,或误写为grid-gap(已废弃) | 直接使用gap,确认浏览器版本 |
| 手机上字体异常大 | 缺少 viewport meta 标签 | 添加<meta name="viewport" ...> |
| Flex 子项长文本把容器撑破 | min-width默认为 auto | 给子项加min-width: 0 |
| 媒体查询写了但不生效 | 断点顺序错误,或优先级冲突 | 移动优先时用min-width,按从小到大的顺序写 |
| 页面高度不满一屏但 Footer 上浮 | Grid 容器没设置min-height: 100vh | 给.page加min-height: 100vh |
其中min-width: 0这个坑我想多说两句。Flex 子项和 Grid 子项在默认情况下都有一个隐式的min-width: auto,意思是“元素最小宽度不能小于内容宽度”。你在元素里放一段不换行的英文单词或者一张固定宽度的图片,它就会强行撑开父容器,导致 Flex 布局失效。解决办法就是在子项上显式设置min-width: 0。这个坑的排查难度在于浏览器控制台不报错,布局就是“怪怪的”。白纸编程的课程里在讲 Flex 时提了一嘴,但我觉得它值得出现在任何一份前端面试题里。
5.2 笔记复盘:学完这三课之后我做了什么
把这三课内容消化完,我给自己布置的一个小任务:不看任何参考,独立完成一个“个人名片页”。页面结构很简单:顶部导航用 Flex,中部主体是一张带头像和文字的名片卡片,底部用 Grid 做了一个两列的链接区,整页在手机宽度下自动转为单列。写完后我意识到,这三课学的不是纯粹的新语法,而是一整套布局思维方式。
以前的我看一个页面,先想用什么标签;现在的我先想这个页面分几块、每块是一维排列还是二维网格。带着这个思路去看任何一个现成网站,都能快速拆解出布局结构。这是我学完 19~21 课最大的收获:不是记住了多少属性,而是建立了一套“拆解页面”的心智模型。
最后分享一个我至今仍在用的小习惯:写任何 CSS 布局之前,先在纸上或文档里画一个简略的区域分布图,标出哪些地方适合 Flex,哪些适合 Grid,哪里需要加媒体查询。不要直接上手写代码,先花五分钟做结构推演。这段时间省下来,后面改布局的时间能省几倍。根据我个人经验,这比记住任何一个属性都值钱。