CSS 教程 / 使用 HTML 与 CSS 构建响应式网站

您可以结合语义化 HTML、共享 CSS 变量、Flexbox 导航和 Grid 卡片布局来构建一个小型响应式网站。从可读的内容和可用的窄屏布局开始,然后添加宽屏调整和可见的交互状态。

定义页面及验收检查

在进行样式设计之前,先确定页面需要的内容和行为。在窄屏幕上,导航在需要时换行,卡片形成一列。更多的可用空间则允许两列或三列。限制整体阅读宽度,以免在宽屏幕上文本过长。

需求 完成标准
内容结构 一个主标题、引言句、三张卡片和页脚按顺序阅读。
窄屏幕 菜单和卡片可以阅读,无需水平滚动
宽屏幕 卡片以多列布局,同时保持最大主体宽度
键盘导航 正文跳转链接、导航和区段链接获得焦点时有可见标记。
内容变化 即使输入没有空格的长描述和字符串,不因固定高度而被裁切。
减少移动 允许根据需要移动,无需平滑滚动和装饰性过渡。

此示例是一个静态屏幕,卡片本身没有链接。介绍/课程菜单和大型导航链接会移动到同一文档的各个部分。要扩展您的实际课程列表,请将已验证的帖子 URL 放在卡片标题中。无需强行添加未使用的表单或无限动画来完成此练习。

准备文件和语义化 HTML

在空文件夹中创建 index.html 和 style.css,并确保它们位于同一位置。创建文件时,请确保它不是 index.html.txt(注意隐藏的扩展名),并以 UTF-8 保存。index.html 中的 link 标签指向同一文件夹中的 style.css。无需单独安装或构建。

learning-site/
  index.html
  style.css

首先,将以下整个 HTML 保存为 index.html。即使 CSS 是空的,标题、链接和卡片内容也必须按顺序读取。header 是网站头部区域,nav 是导航,main 是主要内容,footer 是页脚。在文档中将学习主题 h2 和卡片 h3 放在 h1 下面。此 HTML 是一个独立文件的示例,因此它与直接附加到 WordPress body 的页面结构分开。

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>A Small Learning Site</title>
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <a class="skip-link" href="#main">Skip to main content</a>
  <header class="site-header">
    <div class="container header-inner">
      <p class="site-name">Learning Notes</p>
      <nav aria-label="Main navigation">
        <a href="#intro">About</a><a href="#lessons">Lessons</a>
      </nav>
    </div>
  </header>
  <main id="main" tabindex="-1">
    <section id="intro" class="hero" aria-labelledby="intro-title">
      <div class="container">
        <h1 id="intro-title">Learn to Build Webpages Step by Step</h1>
        <p>Structure content with HTML and adapt its appearance with CSS.</p>
        <a class="button" href="#lessons">Explore the lessons</a>
      </div>
    </section>
    <section id="lessons" class="lessons container" aria-labelledby="lessons-title">
      <h2 id="lessons-title">Learning topics</h2>
      <ul class="card-grid" role="list">
        <li><article class="card"><h3>HTML structure</h3><p>Organize content with headings and paragraphs.</p></article></li>
        <li><article class="card"><h3>CSS styles</h3><p>Adjust colors, fonts, and spacing.</p></article></li>
        <li><article class="card"><h3>Responsive layout</h3><p>Arrange cards to fit the available width.</p></article></li>
      </ul>
    </section>
  </main>
  <footer class="site-footer"><div class="container"><p>Small learning site exercise</p></div></footer>
</body>
</html>

Skip to main content 链接指向 main 的 id。tabindex="-1" 允许 main 在不加入正常 tab 顺序的情况下接收焦点。点击菜单或快捷方式即可导航到该部分,并确保键盘导航从主内容继续。有关快捷方式的用途和组成,请参考 W3C:跳转到正文链接。

设置共享值和内容宽度

使用以下部分开始 style.css。按角色命名颜色变量可以更容易地更改共享强调色。border-box 将内边距和边框包含在声明的宽度内,使尺寸更简单。建立一个可以容纳增长内容的基础布局,而不是到处分配固定尺寸。在更改共享值之前,区分 CSS 变量:var()、作用域、继承和备选值。

/* Shared values and base styles */
:root {
  --ink: #183044;
  --accent: #166b92;
  --surface: #eef6fa;
  --line: #c9dbe4;
  --focus: #164f73;
}
*, *::before, *::after { box-sizing: border-box; }
html { scroll-behavior: smooth; }
body { margin: 0; color: var(--ink); font-family: sans-serif; line-height: 1.6; }
a { color: var(--accent); text-underline-offset: .15em; }
:focus-visible { outline: 3px solid var(--focus); outline-offset: 3px; }
.container { width: min(100% - 2rem, 68rem); margin-inline: auto; }
.container, .card { overflow-wrap: anywhere; }

容器的宽度是“可用宽度减去左右合计 2rem,即每侧 1rem”和68rem两者中较小的值。为窄屏幕提供左右边距,为宽屏幕提供最大宽度。margin-inline的自动值用于居中区域。overflow-wrap: anywhere允许在必要时将不含空格的长地址或标识符在行内换行。必须区分仅增加一般句子长度的练习和添加一个长单词的练习。

如果在此阶段刷新,颜色、默认字体和内容宽度将会被应用,但标题和卡片尚未进行详细布局。如果完全没有任何变化,请在继续添加后续 CSS 区段之前检查文件路径。流式大小和最大宽度的概念与使用 min、max 和 clamp 构建流式 CSS 布局相联系。

构建Flexbox导航和焦点路径

在 style.css 的 Shared values and base styles 区段后追加以下代码:站点名称与菜单之间的布局由 header-inner 管理,菜单内部的间距由 nav 管理。通过将这两个位置之间的间隙分开,可以减少在更改整个头部间距时菜单链接间距意外变化的可能性。轴方向和项目布局的基本原理可以在 Flexbox 基础:容器、项目与轴 中找到。

/* Navigation and header */
.skip-link { position: absolute; left: 1rem; top: -6rem; padding: .5rem; background: white; }
.skip-link:focus { top: 1rem; z-index: 10; }
.site-header { border-bottom: 1px solid var(--line); }
.header-inner { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between;
  gap: .5rem 1.5rem; padding-block: 1rem; }
.site-name { margin: 0; font-weight: 700; }
nav { display: flex; flex-wrap: wrap; gap: 1rem; }

flex-wrap 会在空间不足时将项目放到下一行。在窄屏幕上出现两行菜单并不是失败。判断标准是是否存在重叠、截断或无法点击的链接。应按合理的 HTML 顺序编写导航,不要只用 order 颠倒视觉排列。

正文跳转链接默认放在页面顶部可视区域之外,当它获得焦点时才可见。使用 display:none 隐藏它无法达到同样的目的,因为它甚至无法获得键盘焦点。第一次按 Tab 时应显示 Skip to main content。按 Enter 跳转到 main 后,再按 Tab,焦点应移到介绍区的 Explore the lessons 链接。选择深色焦点轮廓是为了在白色和介绍背景中都能突出显示。

为流式介绍和链接状态设置样式

添加以下部分可完成介绍背景、标题和大型导航链接。标题的 clamp() 限制最小值、首选值与最大值。随着宽度增加,字体不会无限增大,同时字体大小设置,包括 rem 单位,也会被考虑在内。需要单独的屏幕检查,以确定用户放大时是否真的容易阅读。

/* Introduction and navigation link */
.hero { padding-block: clamp(3rem, 8vw, 6rem); background: var(--surface); }
.hero h1 { max-width: 18ch; margin-block: 0 1rem;
  font-size: clamp(1.8rem, 1.2rem + 2vw, 3rem); line-height: 1.25; }
.hero p { max-width: 50ch; }
.button { display: inline-block; margin-top: .5rem; padding: .7rem 1rem;
  border-radius: .4rem; background: var(--accent); color: white; text-decoration: none;
  transition: background-color 180ms ease; }
.button:hover { background: #104d6a; }

标题的 max-width: 18ch 使用字体“0”字符的宽度;它不保证每行 18 个字母。其 line-height 控制标题换行行,而介绍段落限制为 50ch。本示例使用系统无衬线字体,并且不下载外部字体。

对于较大的链接,使用 a,因为它们会将您带到同一文档的课程部分。如果操作是改变状态而不移动页面,则应检查按钮。悬停时仅使用背景颜色过渡,不需要装饰性移动或放大。焦点状态可以通过常见的轮廓确认。

构建能够容纳内容的网格卡片

添加以下部分以完成您的课程卡和页脚。移除列表的默认边距和项目符号,但保留 HTML 中的 ul 和 li 结构。如果带有 list-style: none 的列表位于 nav 之外,它可能不会在 Safari 的辅助功能树中被识别为列表。在示例中,ul 指定 role="list" 来指定列表角色。此情况也可以在 MDN 列表辅助功能描述 中找到。HTML 的阅读顺序不会因网格中行和列的排列而改变。

/* Cards and footer */
.lessons { padding-block: 3rem; }
.lessons h2 { margin-block: 0 1.5rem; }
.card-grid { display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 16rem), 1fr));
  gap: 1rem; margin: 0; padding: 0; list-style: none; }
.card-grid > li { min-width: 0; }
.card { height: 100%; padding: 1.5rem; border: 1px solid var(--line); border-radius: .5rem; }
.card h3 { margin-block: 0 .75rem; }
.card p { margin-block: 0; }
.site-footer { padding-block: 2rem; border-top: 1px solid var(--line); }
.site-footer p { margin: 0; }

CSS Grid:repeat、auto-fit、auto-fill 与命名区域 介绍了自动重复列;本例用 auto-fit 建立可容纳的列轨道,minmax() 决定每列的最小尺寸和可扩展尺寸。当父元素小于 16rem 时,min(100%, 16rem) 的最小值会使卡片缩小到该宽度。剩余空间由 1fr 分配。即使足够宽度能先建立四条重复列轨道,auto-fit 也会折叠未被这三张卡片占用的轨道,不会增加第四张卡片。参见 MDN repeat 函数 和 minmax 函数。

在容器尚未达到 68rem 上限、根字号为 16px 且可用页面宽度与视口相同时,卡片的最小宽度为256px,间距为16px,总左右边距为32px。两列至少需要560px的空间,三列需要832px。随着滚动条、父元素宽度和字体大小的变化,实际的过渡宽度也会变化,因此不要根据设备名称记住它。

卡片高度为100%是指填充同一行列表项高度的值。它不是固定的像素高度,所以当描述内容增加时,行的高度可以增长。为了防止长词挤压Grid项的大小,将li的最小宽度设置为0,并使用卡片的换行规则。

添加宽屏和减少运动条件

将在 style.css 末尾添加此部分。在视口宽度大于或等于 64rem 时,引言背景会更改为渐变。网格继续独立决定卡片列的数量;此媒体查询不设置该数量。

/* Conditional adjustments */
@media (min-width: 64rem) {
  .hero { background: linear-gradient(100deg, var(--surface), #fff); }
}
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .button { transition: none; }
}

在减少运动条件下,我们将滚动更改为即时移动,并且消除背景颜色转换。链接仍可跳转并显示键盘焦点,内容也保持可读。条件规则的应用与 CSS 媒体查询和基于内容的断点 相关联,移动和可访问性检查与 使用宽度、缩放和键盘测试检查响应式 CSS 相关联。

运行完整的样式表

将以下合并后的完整 CSS 保存为 style.css。使用整个文件检查代码顺序或重新开始。将其保存在与 index.html 相同的文件夹中后,用浏览器打开 index.html 并刷新。

/* Shared values and base styles */
:root {
  --ink: #183044;
  --accent: #166b92;
  --surface: #eef6fa;
  --line: #c9dbe4;
  --focus: #164f73;
}
*, *::before, *::after { box-sizing: border-box; }
html { scroll-behavior: smooth; }
body { margin: 0; color: var(--ink); font-family: sans-serif; line-height: 1.6; }
a { color: var(--accent); text-underline-offset: .15em; }
:focus-visible { outline: 3px solid var(--focus); outline-offset: 3px; }
.container { width: min(100% - 2rem, 68rem); margin-inline: auto; }
.container, .card { overflow-wrap: anywhere; }

/* Navigation and header */
.skip-link { position: absolute; left: 1rem; top: -6rem; padding: .5rem; background: white; }
.skip-link:focus { top: 1rem; z-index: 10; }
.site-header { border-bottom: 1px solid var(--line); }
.header-inner { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between;
  gap: .5rem 1.5rem; padding-block: 1rem; }
.site-name { margin: 0; font-weight: 700; }
nav { display: flex; flex-wrap: wrap; gap: 1rem; }

/* Introduction and navigation link */
.hero { padding-block: clamp(3rem, 8vw, 6rem); background: var(--surface); }
.hero h1 { max-width: 18ch; margin-block: 0 1rem;
  font-size: clamp(1.8rem, 1.2rem + 2vw, 3rem); line-height: 1.25; }
.hero p { max-width: 50ch; }
.button { display: inline-block; margin-top: .5rem; padding: .7rem 1rem;
  border-radius: .4rem; background: var(--accent); color: white; text-decoration: none;
  transition: background-color 180ms ease; }
.button:hover { background: #104d6a; }

/* Cards and footer */
.lessons { padding-block: 3rem; }
.lessons h2 { margin-block: 0 1.5rem; }
.card-grid { display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 16rem), 1fr));
  gap: 1rem; margin: 0; padding: 0; list-style: none; }
.card-grid > li { min-width: 0; }
.card { height: 100%; padding: 1.5rem; border: 1px solid var(--line); border-radius: .5rem; }
.card h3 { margin-block: 0 .75rem; }
.card p { margin-block: 0; }
.site-footer { padding-block: 2rem; border-top: 1px solid var(--line); }
.site-footer p { margin: 0; }

/* Conditional adjustments */
@media (min-width: 64rem) {
  .hero { background: linear-gradient(100deg, var(--surface), #fff); }
}
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .button { transition: none; }
}

更改数值并诊断问题

  1. 将屏幕宽度缩小到约 390px,然后再放宽,保持原始数值。对于较小的宽度,应显示一列,对于较大的宽度,应显示两列或三列。如果上述条件相同,也需要比较 559px 和 560px,以及 831px 和 832px 之间的差异。
  2. 仅将 .card-grid 中的 16rem 改为 20rem。在同一屏幕上增加更多列需要更多空间。确认后恢复为 16rem。
  3. 仅将容器的 68rem 改为 50rem。即使在更大的屏幕上,整体内容宽度也会变窄。确认后恢复为 68rem。
  4. 仅把 :root 的 --accent 改为另一种足够深的颜色。查看链接和按钮背景是否同时改变,并恢复它们的原始颜色。白色按钮文本和焦点指示器在新背景下也应可读。
  5. 将卡片描述扩展为几句完整的句子。确保随着高度增加内容不会被截断,并且同一行的卡片高度保持统一。练习后,恢复原来的句子。
  6. 在卡片中插入一个没有空格的英文字符串。保留 overflow-wrap: anywhere 时,长字符串会在卡片内换行。仅暂时移除该换行规则可能会导致溢出。比较之后,规则和句子都恢复。
  7. 使用 Tab 和 Shift+Tab 移动,并按 Enter 访问每个链接。你应该能够在无需鼠标的情况下导航到介绍和学习主题。
  8. 重现动画减少条件。链接的跳转功能应保持,同时平滑滚动/过渡效果应消失。确认后,将模拟设置恢复为默认值。

检查在浏览器缩放或字体设置更改时,导航、卡片、焦点指示器和段落是否仍然可读。暂时在开发工具中放大html的字体大小可以测试基于rem的尺寸,但这与浏览器缩放不同。一次成功的设置并不能保证在所有缩放和文本大小条件下的可用性。

问题 首先检查
完全没有样式 文件扩展名、保存状态、链接的相对路径
发生水平滚动 长单词,最小列宽,父元素实际宽度,缺少 border-box
卡片高度参差不齐 设置同一行和卡片的高度;不同的行不必相同
没有可见的焦点指示器 覆盖 outline 的规则,以及 overflow 是否裁掉外轮廓
不需要的移动效果 实际的媒体条件以及应用的 scroll-behavior·transition 值

在开发者工具中选择有问题的元素,以查看实际计算的宽度、选择器和网格列。不要通过强制所有换行或在 body 上添加 overflow:hidden 来掩盖问题,而应首先修复溢出元素的大小和内容。通过检查每个需求的基础状态、变化状态和恢复状态,你可以比复制完成的代码更进一步,并将其应用于其他内容。