CSS3 折叠教程:从零开始打造手风琴组件

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

css3折叠教程
(图片来源网络,侵删)

我们将学习两种实现方式:

  1. 纯 CSS 实现:现代、简洁,无需 JavaScript。
  2. CSS + JavaScript 实现:更灵活,功能更强大。

核心原理

无论是哪种方式,折叠组件都由以下两部分构成:

  • 触发器:通常是标题(<h3>),用户点击它。
  • 内容面板:通常是 <div><p>,包含要显示/隐藏的信息。

实现折叠的核心是 CSS 的 target 伪类max-height 属性的动画,以及 JavaScript 的 classList 操作


方法一:纯 CSS 实现 (利用 target 伪类)

这种方法非常巧妙,它利用了浏览器自身的 URL 锚点功能,当用户点击一个链接指向页面内的某个元素时,该元素就会成为 target

css3折叠教程
(图片来源网络,侵删)

优点:

  • 代码量少,无需 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;
}

代码解析:

  1. .accordion-content { max-height: 0; overflow: hidden; }:默认将内容面板的高度设为 0,并隐藏溢出内容。
  2. .accordion-content { transition: ... }:为 max-height 属性变化添加过渡动画,使其平滑。
  3. .accordion-content:target { max-height: 500px; }:这是核心!当用户点击 <a href="#section1"> 时,id="section1" 的元素就成为 target,我们将其 max-height 设置为一个很大的值,内容就会“展开”出来。

方法二:CSS + JavaScript 实现 (更通用)

这是最常见、最灵活的实现方式,不依赖 URL 锚点,且更容易控制。

css3折叠教程
(图片来源网络,侵删)

优点:

  • URL 保持干净,没有 锚点。
  • 可以更精细地控制动画和状态。
  • 更易于与后端数据结合。

步骤 1:HTML 结构

HTML 结构可以更简单,不需要 hrefid

<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;
}

代码解析:

  1. 我们定义了一个 .active 类,这个类会控制内容的显示和隐藏。
  2. 初始状态下,没有 .active 类,内容是隐藏的。
  3. 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');
      });
    });
  });
});

代码解析:

  1. querySelectorAll('.accordion-trigger-js'):获取所有标题元素。
  2. forEach 遍历每个标题,并为其添加 click 事件。
  3. trigger.nextElementSibling:巧妙地获取到紧邻其后的内容面板 <div>
  4. classList.toggle('active'):这是核心方法,如果元素有 active 类,则移除它;如果没有,则添加它,这完美实现了“展开/收起”的切换。
  5. 可选步骤 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-heightheight
可访问性 需要额外 ARIA 支持 更容易添加 ARIA 支持
推荐场景 简单的、不需要复杂交互的组件 几乎所有现代 Web 项目

对于初学者,方法二(CSS + JavaScript) 是更值得学习和掌握的方案,因为它更通用、更灵活,是行业标准,希望这份教程对你有帮助!