CSS3 折叠教程:从零开始打造手风琴组件
“折叠”或“手风琴”(Accordion)是一种常见的 UI 组件,用于在有限的空间内展示大量信息,用户点击一个标题时,其对应的内容区域会展开或收起,同时其他区域会自动关闭。

(图片来源网络,侵删)
我们将学习两种实现方式:
- 纯 CSS 实现:现代、简洁,无需 JavaScript。
- CSS + JavaScript 实现:更灵活,功能更强大。
核心原理
无论是哪种方式,折叠组件都由以下两部分构成:
- 触发器:通常是标题(
<h3>),用户点击它。 - 内容面板:通常是
<div>或<p>,包含要显示/隐藏的信息。
实现折叠的核心是 CSS 的 target 伪类 或 max-height 属性的动画,以及 JavaScript 的 classList 操作。
方法一:纯 CSS 实现 (利用 target 伪类)
这种方法非常巧妙,它利用了浏览器自身的 URL 锚点功能,当用户点击一个链接指向页面内的某个元素时,该元素就会成为 target。

(图片来源网络,侵删)
优点:
- 代码量少,无需 JavaScript。
- 语义化好,可以直接使用
<a>
缺点:
- URL 会改变,带有 锚点。
- 一次只能展开一个面板(这通常是手风琴的特性,所以是优点)。
- 对 SEO 和可访问性有一定影响(但可通过 ARIA 属性改善)。
步骤 1:HTML 结构
我们需要一个容器,里面包含多个“触发器”和“内容面板”的组合,关键点:
- 触发器使用
<a>标签,href指向对应内容面板的id,面板使用<div>,并设置一个唯一的id。
<div class="accordion">
<!-- 第一个折叠项 -->
<div class="accordion-item">
<a href="#section1" class="accordion-trigger">第一章:CSS 的起源</a>
<div id="section1" class="accordion-content">
<p>CSS(层叠样式表)是一种用来表现 HTML 或 XML 等文件样式的计算机语言,CSS 不仅可以静态地修饰网页,还可以配合各种脚本语言对网页中各元素进行格式化。</p>
</div>
</div>
<!-- 第二个折叠项 -->
<div class="accordion-item">
<a href="#section2" class="accordion-trigger">第二章:选择器</a>
<div id="section2" class="accordion-content">
<p>CSS 选择器是 CSS 规则的一部分,它用于选择你想要样式化的 HTML 元素,常见的选择器包括元素选择器、类选择器、ID 选择器和属性选择器。</p>
</div>
</div>
<!-- 第三个折叠项 -->
<div class="accordion-item">
<a href="#section3" class="accordion-trigger">第三章:盒模型</a>
<div id="section3" class="accordion-content">
<p>CSS 中的盒模型是网页布局的基础,它将每个 HTML 元素看作一个矩形的盒子,这个盒子由内容、内边距、边框和外边距四部分组成。</p>
</div>
</div>
</div>
步骤 2:CSS 样式
这是实现折叠效果的关键。
/* 基础样式,让布局更整洁 */
.accordion {
max-width: 600px;
margin: 2rem auto;
border: 1px solid #ccc;
border-radius: 8px;
overflow: hidden; /* 防止内容溢出圆角 */
}
.accordion-item {
border-bottom: 1px solid #eee;
}
.accordion-item:last-child {
border-bottom: none;
}
/* 触发器样式 */
.accordion-trigger {
display: block;
padding: 1rem;
text-decoration: none;
background-color: #f7f7f7;
color: #333;
font-weight: bold;
transition: background-color 0.3s ease;
}
.accordion-trigger:hover {
background-color: #e9e9e9;
}
/* 默认状态下,内容面板是隐藏的 */
.accordion-content {
max-height: 0;
overflow: hidden; /* 隐藏超出部分 */
transition: max-height 0.3s ease-out; /* 添加平滑过渡效果 */
background-color: #fff;
}
面板的 ID 成为 :target 时,它就会展开 */
.accordion-content:target {
max-height: 500px; /* 设置一个足够大的值,确保内容能完全显示 */
transition: max-height 0.5s ease-in; /* 展开时的过渡 */
}
/* 为了美观,可以给当前激活的触发器加个样式 */
.accordion-trigger:target {
background-color: #d4e6f1;
}
代码解析:
.accordion-content { max-height: 0; overflow: hidden; }:默认将内容面板的高度设为 0,并隐藏溢出内容。.accordion-content { transition: ... }:为max-height属性变化添加过渡动画,使其平滑。.accordion-content:target { max-height: 500px; }:这是核心!当用户点击<a href="#section1">时,id="section1"的元素就成为target,我们将其max-height设置为一个很大的值,内容就会“展开”出来。
方法二:CSS + JavaScript 实现 (更通用)
这是最常见、最灵活的实现方式,不依赖 URL 锚点,且更容易控制。

(图片来源网络,侵删)
优点:
- URL 保持干净,没有 锚点。
- 可以更精细地控制动画和状态。
- 更易于与后端数据结合。
步骤 1:HTML 结构
HTML 结构可以更简单,不需要 href 和 id。
<div class="accordion-js">
<div class="accordion-item-js">
<h3 class="accordion-trigger-js">第一章:CSS 的起源</h3>
<div class="accordion-content-js">
<p>CSS(层叠样式表)是一种用来表现 HTML 或 XML 等文件样式的计算机语言...</p>
</div>
</div>
<div class="accordion-item-js">
<h3 class="accordion-trigger-js">第二章:选择器</h3>
<div class="accordion-content-js">
<p>CSS 选择器是 CSS 规则的一部分,它用于选择你想要样式化的 HTML 元素...</p>
</div>
</div>
<div class="accordion-item-js">
<h3 class="accordion-trigger-js">第三章:盒模型</h3>
<div class="accordion-content-js">
<p>CSS 中的盒模型是网页布局的基础...</p>
</div>
</div>
</div>
步骤 2:CSS 样式
CSS 与方法一非常相似,只是不再依赖 target。
.accordion-js {
max-width: 600px;
margin: 2rem auto;
border: 1px solid #ccc;
border-radius: 8px;
overflow: hidden;
}
.accordion-item-js {
border-bottom: 1px solid #eee;
}
.accordion-item-js:last-child {
border-bottom: none;
}
.accordion-trigger-js {
margin: 0;
padding: 1rem;
background-color: #f7f7f7;
cursor: pointer; /* 鼠标悬停时显示手型 */
font-weight: bold;
transition: background-color 0.3s ease;
}
.accordion-trigger-js:hover {
background-color: #e9e9e9;
}
/* 默认隐藏内容 */
.accordion-content-js {
max-height: 0;
overflow: hidden;
transition: max-height 0.3s ease-out, padding 0.3s ease-out;
background-color: #fff;
padding: 0 1rem; /* 默认内边距为0,展开时再设置 */
}
面板被激活时,添加一个 active 类 */
.accordion-content-js.active {
max-height: 500px; /* 足够大的值 */
padding: 1rem; /* 展开时恢复内边距 */
transition: max-height 0.5s ease-in, padding 0.5s ease-in;
}
/* 可选:给激活的标题加样式 */
.accordion-trigger-js.active {
background-color: #d4e6f1;
}
代码解析:
- 我们定义了一个
.active类,这个类会控制内容的显示和隐藏。 - 初始状态下,没有
.active类,内容是隐藏的。 - JavaScript 的任务就是:当用户点击标题时,给对应的内容面板添加或移除
.active类。
步骤 3:JavaScript 逻辑
这是实现交互的核心。
document.addEventListener('DOMContentLoaded', () => {
// 1. 获取所有触发器
const triggers = document.querySelectorAll('.accordion-trigger-js');
// 2. 为每个触发器添加点击事件监听器
triggers.forEach(trigger => {
trigger.addEventListener('click', () => {
// 3. 获取当前触发器对应的内容面板
const content = trigger.nextElementSibling;
// 4. 切换内容面板的 'active' 类
content.classList.toggle('active');
// 5. (可选) 切换触发器自身的 'active' 类
trigger.classList.toggle('active');
// 6. (可选) 实现点击一个,关闭其他所有面板(手风琴效果)
// 先遍历所有触发器
triggers.forEach(otherTrigger => {
// 如果是当前点击的触发器,则跳过
if (otherTrigger === trigger) {
return;
}
// 否则,移除其兄弟面板和自身的 active 类
otherTrigger.classList.remove('active');
otherTrigger.nextElementSibling.classList.remove('active');
});
});
});
});
代码解析:
querySelectorAll('.accordion-trigger-js'):获取所有标题元素。forEach遍历每个标题,并为其添加click事件。trigger.nextElementSibling:巧妙地获取到紧邻其后的内容面板<div>。classList.toggle('active'):这是核心方法,如果元素有active类,则移除它;如果没有,则添加它,这完美实现了“展开/收起”的切换。- 可选步骤 6:这部分代码实现了“手风琴”的核心逻辑——一次只打开一个面板,它会先关闭所有其他面板,然后再处理当前点击的。
高级技巧与最佳实践
添加图标(+ / -)
旁添加一个图标,可以更直观地表示状态。
HTML:
<h3 class="accordion-trigger-js"> <span>第一章:CSS 的起源</span> <span class="icon">+</span> </h3>
CSS:
.accordion-trigger-js {
/* ... 其他样式 ... */
display: flex;
justify-content: space-between;
align-items: center;
}
.icon {
font-size: 1.5rem;
transition: transform 0.3s ease;
}
展开时,旋转图标 */
.accordion-trigger-js.active .icon {
transform: rotate(45deg);
}
改善可访问性 (Accessibility - ARIA)
为了让屏幕阅读器等辅助技术能理解你的组件,应该添加 ARIA 属性。
HTML:
<h3 class="accordion-trigger-js"
aria-expanded="false"
aria-controls="content1">
第一章:CSS 的起源
</h3>
<div id="content1" class="accordion-content-js" aria-hidden="true">
<!-- 内容 -->
</div>
JavaScript:
在切换 active 类的同时,也要更新 ARIA 属性。
// 在 toggle 'active' 类之后
const isExpanded = content.classList.contains('active');
trigger.setAttribute('aria-expanded', isExpanded);
content.setAttribute('aria-hidden', !isExpanded);
aria-expanded="true/false":告诉辅助技术,这个按钮当前是展开还是收起状态。aria-controls="content1":告诉辅助技术,这个按钮控制着哪个元素。aria-hidden="true/false":告诉辅助技术,这个内容区域当前是否应该被忽略。
使用 max-height 的问题
max-height: 500px 是一个“魔术数字”,如果内容超过 500px,就会被截断,更优雅的解决方案是使用 JavaScript 动态计算内容高度,但这会使代码复杂化,对于大多数情况,设置一个足够大的 max-height (如 1000px) 是一个简单有效的折中方案。
| 特性 | 纯 CSS (target) |
CSS + JavaScript |
|---|---|---|
| 实现原理 | 利用 URL 锚点和 target 伪类 |
通过 classList 动态添加/移除类 |
| 代码量 | 少 | 中等 |
| URL | 会改变,带 锚点 | 保持干净 |
| 灵活性 | 较低,依赖 HTML 结构 | 高,可精细控制 |
| 动画 | 依赖 max-height |
依赖 max-height 或 height |
| 可访问性 | 需要额外 ARIA 支持 | 更容易添加 ARIA 支持 |
| 推荐场景 | 简单的、不需要复杂交互的组件 | 几乎所有现代 Web 项目 |
对于初学者,方法二(CSS + JavaScript) 是更值得学习和掌握的方案,因为它更通用、更灵活,是行业标准,希望这份教程对你有帮助!
