HW01 Layout
网页空间布局
从盒模型出发,用 Grid 和 Flex 组织页面,再用容器查询完成自适应重排。
网页空间布局要回答三个问题:一个元素占多大空间?多个元素如何排列?容器变窄时,内容如何重新分配空间?本章按“算尺寸 → 看流动 → 划区域 → 排内容 → 做响应式 → 验证”的顺序,把这些问题拆成可以观察和修改的 CSS 规则。
每个布局概念都有一组独立的“讲解 / 关键代码 / 展示”标签。先看讲解,再对照代码,最后在展示中改变参数或观察结构;章末的验证流程按连续正文展开,方便从头到尾执行。
从盒模型开始:先算清空间
布局的最小单位是盒子。只有先分清内容、内边距、边框和外边距分别占据哪一层,后续的宽度计算才不会靠猜。
内部空间和外部空间
一个块级元素从内向外可以看成四层:content 是文字或子元素所在的内容区,padding 是内容与边框之间的内部留白,border 是可见边界,margin 则是这个盒子与其他盒子的外部距离。
最容易出现误判的是 width。默认的 content-box 会把 padding 和 border 额外加到声明宽度上;border-box 则把它们包含在声明的宽度里。实际项目通常会在全局设置 box-sizing: border-box,让“写下的宽度”更接近“页面上看到的宽度”。
padding 和 margin 都是留白,但职责不同:前者扩大内容与边界之间的呼吸空间,后者拉开不同盒子之间的距离。Grid 或 Flex 子项之间的统一间距优先使用 gap,不要给每个子项分别加 margin。
一个可复用的判断顺序
- 先问元素的可见盒子应该多宽,再决定使用
border-box还是额外计算内容宽度。 - 再区分留白是在盒子里面(
padding)还是盒子外面(margin/gap)。 - 如果内容可能很长,在 Grid 子项上配合
min-width: 0,避免最小内容尺寸把父级撑出横向滚动。
建立一个可计算的盒子
<article class="card"> <h2>可计算的卡片</h2> <p>内容区、内边距和边框共同组成可见盒子。</p></article>*, *::before, *::after { box-sizing: border-box;}
.card { width: 320px; padding: 20px; border: 2px solid #93c5fd; margin: 24px;}在这段代码中,卡片的可见宽度是 320px,内容区会自动减去左右 padding 和 border;margin 不属于可见盒子,它只在外部占据空间。若移除 box-sizing: border-box,默认的 content-box 会让实际可见宽度变成 364px(320 + 40 + 4)。
观察四层空间
下面的示意把一张卡片拆成 margin → border → padding → content 四层。重点观察:改变内部留白不会改变 border-box 的总宽度,外部的 margin 仍然留在盒子之外。
320px border-box
先理解正常流,再选择定位
浏览器在没有 Grid、Flex 或定位规则时,仍会按照正常文档流排列内容。先利用默认流完成可读结构,再选择新的布局模式,通常比一开始就用坐标固定元素更稳定。
默认布局已经有秩序
正常流中,块级盒子沿块轴依次排列,通常占满容器的可用内联宽度;行内盒子则跟随文字排在同一行,空间不足时自然换行。display: grid 和 display: flex 会为容器建立新的布局模式,但容器本身仍然处在外部文档流中。
position: relative 会保留元素原来的占位,也常用来为绝对定位的子元素建立包含块;position: absolute 和 fixed 会让元素脱离正常流,周围内容不再为它预留空间。因此,绝对定位适合角标、浮层和局部叠加,不适合承担正文、侧栏等页面骨架。
position: sticky 适合目录或工具栏:元素在达到指定阈值前仍随文档流移动,达到阈值后保持在容器范围内。无论使用哪种定位,都要检查覆盖关系、滚动容器和键盘焦点是否仍然可见。
让内容决定高度
正文区域尽量避免固定高度。文字变长时,盒子默认会增长;如果确实需要滚动区域,使用明确的尺寸约束和 overflow: auto。不要用 overflow: hidden 掩盖布局错误,否则文字和可聚焦控件可能一起被裁掉。
只把角标移出正常流
<article class="card"> <span class="card-badge">NEW</span> <h2>课程公告</h2> <p>正文继续参与正常流,并决定卡片高度。</p></article>.card { position: relative; padding: 24px;}
.card-badge { position: absolute; inset-block-start: 12px; inset-inline-end: 12px;}
.scroll-region { max-width: 100%; overflow-x: auto;}父元素的 position: relative 建立定位上下文,但它仍占据正常流中的位置。只有角标脱离正常流;标题和正文仍会自然撑开卡片。
对比流内排列与局部叠加
左侧的标题、正文和操作区按正常流依次占位;右侧只有角标绝对定位在卡片右上角,正文仍按原顺序排列。
定位改变的是局部叠加关系,不应替代正文的自然流动。
用 Grid 建立页面骨架
当问题同时涉及行和列时,CSS Grid 能把页面区域放到同一张二维网格上。先分配页面骨架,再在主内容内部嵌套更小的 Grid,层级会比不断堆叠 margin 更清晰。
把空间写成轨道
Grid 容器由行、列和它们之间的 gutter(间距)组成。grid-template-columns: 160px minmax(0, 1fr) 表示侧栏固定为 160px,主内容占据剩余空间;minmax(0, 1fr) 的 0 明确允许主内容列收缩,是处理长标题、代码块和表格时很有用的防溢出习惯。
如果页面区域有明确语义,可以使用 grid-template-areas:header、sidebar、main、footer 直接表达布局意图;如果只是重复卡片,则使用 repeat() 和 minmax() 描述可重复的列更合适。
Grid 主要负责“区域之间如何占空间”,不是用来替代 HTML 语义。页面仍应使用 header、aside、main、footer 等结构元素,再用 CSS Grid 改变它们的视觉位置。
用命名区域搭出骨架
<div class="page"> <header class="page-header">站点标题与导航</header> <aside class="page-sidebar">筛选与说明</aside> <main class="page-main"> <div class="cards"> <article>内容 A</article> <article>内容 B</article> <article>内容 C</article> </div> </main> <footer class="page-footer">补充信息</footer></div>.page { display: grid; grid-template-columns: 160px minmax(0, 1fr); grid-template-areas: "header header" "sidebar main" "footer footer"; gap: 16px;}
.page-header { grid-area: header; }.page-sidebar { grid-area: sidebar; }.page-main { grid-area: main; min-width: 0; }.page-footer { grid-area: footer; }
.cards { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 16px;}观察行、列与区域
这个示例用命名区域搭出一个真实页面:页头和页脚跨越整行,侧栏占固定轨道,主内容与三张卡片占据剩余空间。
grid-template-areas 把视觉区域映射到页面结构,内层卡片 Grid 再独立管理三列。
用 Flex 处理一维对齐
当元素主要沿一条轴线排列时,用 Flex 解决对齐和剩余空间分配更直接。导航、工具栏、按钮组和卡片内部的标题区,通常都是 Flex 的使用场景。
先确定主轴,再处理交叉轴
display: flex 默认沿水平方向排列子项。flex-direction 决定主轴方向,justify-content 管主轴上的分配,align-items 管交叉轴上的对齐;这两个属性的含义会随着主轴方向改变。
工具栏内容长度不一时,常见组合是:容器使用 align-items: center,项目之间用 gap,需要两端分布时使用 justify-content: space-between,按钮较多时补上 flex-wrap: wrap。需要“标题占据剩余空间、按钮保持自身宽度”时,让标题 flex: 1,并在可能被长文字撑开的项目上设置 min-width: 0。
Grid 和 Flex 可以嵌套:Grid 负责页面的大区域,Flex 负责区域内部的一排控件。选择布局方法时,看的是空间关系,而不是组件名称。
让标题和按钮共享一行
<div class="toolbar"> <div class="toolbar-title"> <strong>布局实验</strong> <span>当前视图:宽屏</span> </div> <div class="toolbar-actions"> <button type="button">重置</button> <button type="button">保存</button> </div></div>.toolbar { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 12px 20px;}
.toolbar-title { display: flex; align-items: baseline; flex: 1 1 220px; min-width: 0; gap: 12px;}
.toolbar-actions { display: flex; flex: 0 0 auto; gap: 8px;}观察主轴上的剩余空间
示例把标题区和操作区放在同一条 Flex 轴上。点击“窄屏”缩小展示区后,flex-wrap 允许操作区换到下一行,内容不会因为强行保持一行而溢出;点击“宽屏”可以恢复初始布局。
宽屏:标题区占据剩余空间,操作按钮保持在同一行。
用容器查询做自适应重排
响应式布局不是简单地“缩小所有东西”,而是根据可用空间改变区域关系:侧栏可以移到正文上方,三列卡片可以变成单列,工具栏也可以换行。这里用容器查询关注组件自身的宽度,而不是只能关注整个浏览器视口。
让组件根据自己的容器适应
先在希望被观察的父元素上声明 container-type: inline-size,再用 @container 查询它的内联尺寸。这样,同一张卡片放在宽主内容区或窄侧栏中,都能根据自己的实际宽度改变布局。
容器查询适合可复用组件;@media 仍然适合控制整个页面的视口级变化,例如导航是否显示、页面边距如何变化。两者可以同时存在,但断点要对应具体的空间问题,不要为了设备名称硬套一组数字。
响应式规则最好先写“窄空间下的安全状态”,再在更宽的容器中增加列数。每个断点都应该回答一个可观察的问题:卡片是否已经无法舒适地并排?侧栏是否已经挤压了正文?
声明容器并在断点重排
.content-region { container-type: inline-size;}
.content-layout,.card-list { display: grid; grid-template-columns: 160px minmax(0, 1fr); gap: 16px;}
.card-list { grid-template-columns: repeat(3, minmax(0, 1fr));}
@container (max-width: 560px) { .content-layout, .card-list { grid-template-columns: 1fr; }}@container 查询的是 .content-region 的宽度。展示区里把预览宽度切换为 360px,就能在不改变浏览器窗口的情况下触发这条规则。
拖动参数,观察重排
先在宽屏下调大侧栏和区域间距,再切换到 360px 窄屏。注意观察:窄屏下侧栏宽度不再参与左右分栏,页面改为上下流动;同时三张卡片变成单列。
宽屏:侧栏固定宽度,主内容占据剩余空间。
窄屏下侧栏与正文改为上下排列,卡片也变为单列。拖动“区域间距”,区分元素之间的 gap 与元素内部的 padding。
把布局问题变成验证流程
布局完成不等于布局可靠。把“看起来差不多”改成几个可以在浏览器中逐项检查的条件,才能发现溢出、对齐和响应式重排的问题。
四步检查法
- 看结构:用开发者工具检查 Grid 的行、列、区域和 Flex 的主轴,确认视觉层级没有脱离 HTML 语义。
- 看尺寸:从外向内检查 margin、border、padding、content,确认宽度计算符合
box-sizing的选择。 - 看极端内容:输入长标题、长单词、按钮组和空状态,检查是否出现横向滚动、重叠或不可点击的控件。
- 看空间变化:拖动容器宽度,验证每个
@container断点是否解决了一个具体问题;再用键盘操作 Tab,确认布局变化没有隐藏焦点。
把可调参数集中起来
.layout-sandbox { --content-max: 72rem; --space: 16px; --sidebar: 160px; width: min(100%, var(--content-max)); margin-inline: auto; padding-inline: var(--space);}
.layout-sandbox__body { display: grid; grid-template-columns: var(--sidebar) minmax(0, 1fr); gap: var(--space);}
@container (max-width: 560px) { .layout-sandbox__body { grid-template-columns: 1fr; }}把宽度和间距抽成自定义属性后,可以从实验台或调试面板统一修改;当页面出现问题时,也更容易定位是轨道、间距还是盒模型导致的。
最终验收清单
在完成一个页面后,至少确认以下结果:
- 宽屏下主要区域边界清楚,主内容可以收缩。
- Grid 负责二维区域,Flex 负责一维对齐,
gap表达兄弟元素间距。 - 长内容不会撑破容器,也没有不必要的横向滚动。
- 窄容器下布局按预期重排,所有内容仍然可见、可读、可操作。
教程参考
本章内容参考 MDN 的官方学习材料:CSS layout、The box model、Block and inline layout in normal flow、Positioning、CSS grid layout、Flexbox、CSS container queries 和 Overflowing content。可以按本章顺序继续阅读,并用浏览器开发者工具复现其中的例子。