30-seconds-of-css 实战:用纯 CSS 实现汉堡菜单到叉号按钮的悬停过渡动画
30-seconds-of-css 实战:用纯 CSS 实现汉堡菜单到叉号按钮的悬停过渡动画
导读
本文基于 30-seconds-of-css 仓库中的 hamburger-button.md 片段,系统讲解如何仅用 HTML 与 CSS(零 JavaScript)实现经典的"汉堡菜单(Hamburger Menu)"在悬停时平滑过渡为"叉号(Cross)"的交互效果。你将掌握 flex 弹性布局下的三横条结构搭建、transform: rotate() 与 transform-origin 的旋转轴心控制,以及 transition 过渡动画的完整用法,并能在此基础上自由定制尺寸、颜色与动画时长,直接应用到导航栏、移动端菜单按钮等真实场景。
效果概述:从三横线到叉号
汉堡按钮是移动端界面中用于唤起菜单的通用视觉符号——三条水平短杠。本片段实现的是:鼠标悬停(:hover)时,上下两条横杠分别旋转 45° 与 -45° 形成叉号的两条斜边,中间的横杠淡出,从而呈现从"菜单图标"到"关闭按钮"的形态切换。
初始状态 hover 状态
━━━ ╲
━━━ → ·
━━━ ╱
整个过程完全由 CSS 完成,无需 JavaScript 事件监听,代码量少、无依赖、可直接内联到任意页面,契合 30-seconds-of-css 项目"Short CSS code snippets for all your development needs"的定位。
HTML 结构:容器 + 三条横杠
片段给出的标记非常简洁,一个 .hamburger-menu 容器内包裹三个子元素,分别代表顶部、中部与底部横杠:
<div class="hamburger-menu">
<div class="bar top"></div>
<div class="bar middle"></div>
<div class="bar bottom"></div>
</div>
结构要点:
- 容器 div 负责整体布局与尺寸,同时作为
:hover的触发区域(鼠标进入容器任意位置即触发动画); - 三个
.bar子元素分别通过top、middle、bottom三个附加类进行差异化控制——悬停时只有top和bottom发生旋转,middle负责淡出; - 该片段不依赖 JavaScript,也无需表单语义;如果需要更高的无障碍水平,可自行在外层包裹
<button>或添加aria-label,但原片段本身刻意保持最小化。
容器布局:用 flex 让三根横杠自动均匀分布
容器部分的核心在于 flex 布局的轴对齐能力:
.hamburger-menu {
display: flex;
flex-flow: column wrap;
justify-content: space-between;
height: 2.5rem;
width: 2.5rem;
cursor: pointer;
}
逐个属性拆解:
| 属性 | 作用 |
|---|---|
display: flex |
将容器变为弹性盒子,子元素沿主轴排列 |
flex-flow: column wrap |
主轴方向设为纵向(column),允许换行(wrap),是 flex-direction 与 flex-wrap 的简写 |
justify-content: space-between |
在纵轴上把三条横杠均匀分布,首尾两端贴齐容器上下边缘,中间横杠居中——这是保证三条横杠"自动有间距"的关键,无需手写 margin |
height / width: 2.5rem |
固定容器为正方形(40px),决定按钮的点击热区与整体尺寸 |
cursor: pointer |
悬停时显示手型光标,提示用户可点击 |
之所以使用 justify-content: space-between 而非 flex-direction: column 加固定 gap,是因为它能在不显式计算间距的情况下,让三条横杠在容器高度内自动等距分布,是这类"三段式"图标最省事的排布方案。
横杠样式:圆角胶囊 + 左端旋转轴心
每条横杠的公共样式如下:
.hamburger-menu .bar {
height: 5px;
background: black;
border-radius: 5px;
margin: 3px 0px;
transform-origin: left;
transition: all 0.5s;
}
关键点:
height: 5px:横杠厚度,容器 40px 高度除以三根杠加间距,视觉上比例适中;宽度未显式设置,由 flex 布局自动拉伸填满容器;border-radius: 5px:圆角取与高度相同的值,形成两端浑圆的胶囊形横杠,更接近现代 UI 风格;margin: 3px 0px:为横杠提供微小的纵向呼吸空间,避免与容器边缘贴死;transform-origin: left:将变换原点设到元素左端。旋转默认绕元素几何中心进行,而本片段需要上下两条横杠围绕左端旋转,才能形成叉号两条边交汇于容器中心偏左处的视觉效果——这是整个动画观感正确的核心属性;transition: all 0.5s:对元素的所有可动画属性(旋转、透明度等)施加 0.5 秒的过渡,all是简写,实际生效属性主要为transform与opacity。
悬停动画:上下旋转 45°,中间淡出
动画逻辑全部集中在容器处于 :hover 状态时的三条规则中:
.hamburger-menu:hover .top {
transform: rotate(45deg);
}
.hamburger-menu:hover .middle {
opacity: 0;
}
.hamburger-menu:hover .bottom {
transform: rotate(-45deg);
}
机制说明:
.top旋转 45°:围绕左端顺时针旋转 45°,原本水平的横杠变为向右下倾斜的斜线;.bottom旋转 -45°:围绕左端逆时针旋转 45°,变为向右上倾斜的斜线;两条斜线与容器左边缘的交点重合,共同构成"×"的左半边;.middle透明度归零:中间横杠通过opacity: 0淡出。之所以用opacity而不是display: none,是因为display不可参与过渡动画,而opacity可以由transition: all 0.5s平滑驱动;- 由于
.bar上已经声明了transition: all 0.5s,悬停时transform与opacity的变化都会以 0.5 秒的时长平滑过渡,无需在 hover 规则中重复声明。
与仓库中其他依赖 transform-origin 的片段相互印证:例如 hover-underline-animation.md 同样使用 transform: scaleX(0) 加 transform-origin 实现从左侧展开的下划线动画,可见"控制变换原点 + 过渡变换属性"是 30-seconds-of-css 中制作悬停微交互的通用范式。
完整可运行代码
将以下代码合并保存为一个 HTML 文件,用浏览器打开即可看到效果(无需构建工具,无需 JavaScript):
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hamburger Button 演示</title>
<style>
body {
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
margin: 0;
}
.hamburger-menu {
display: flex;
flex-flow: column wrap;
justify-content: space-between;
height: 2.5rem;
width: 2.5rem;
cursor: pointer;
}
.hamburger-menu .bar {
height: 5px;
background: black;
border-radius: 5px;
margin: 3px 0px;
transform-origin: left;
transition: all 0.5s;
}
.hamburger-menu:hover .top {
transform: rotate(45deg);
}
.hamburger-menu:hover .middle {
opacity: 0;
}
.hamburger-menu:hover .bottom {
transform: rotate(-45deg);
}
</style>
</head>
<body>
<div class="hamburger-menu">
<div class="bar top"></div>
<div class="bar middle"></div>
<div class="bar bottom"></div>
</div>
</body>
</html>
外层 body 的 flex 居中只是为了演示方便,可替换为任意实际布局。
定制与进阶
调整尺寸与配色
- 按钮整体大小:修改容器的
width与height(如改为3rem),横杠会随 flex 布局自动拉伸; - 横杠粗细:调整
.bar的height(如6px),同时将border-radius改为与高度一致的值以保持胶囊造型; - 颜色:替换
.bar的background(如#0087ca),注意与页面背景保持足够对比度。
调整动画节奏
- 时长:修改
.bar的transition: all 0.5s中的0.5s,更短(如0.3s)显得干脆,更长(如0.8s)显得柔和; - 缓动函数:可将
all 0.5s扩展为all 0.5s ease-in-out等缓动曲线,改善动画手感; - 旋转角度:理论上可用其他角度组合,但 45° 与 -45° 是构成标准叉号的最优选择,角度偏差会破坏两条斜边的对称性。
改造为点击切换
原片段基于 :hover 设计,若希望按钮"点击一次变为叉号、再点恢复",需要引入少量 JavaScript 切换类名(例如给容器添加 .active 类,并将 :hover 规则改写为 .active 规则);仅用纯 CSS 时,:hover 是达成该效果最简洁的触发器。
与其他片段的组合
- 结合 triangle.md 中"利用 border 与旋转构造纯 CSS 图形"的思路,可将横杠替换为其他形状,探索更多图标变体;
- 参考 button-border-animation.md 的
::before/::after技巧,也可以把三条横杠改造成两个伪元素实现的极简版本(上下两条由伪元素绘制、中间一条隐藏),从而进一步减少 DOM 节点。
仓库定位与本片段的适用说明
本片段来自 30-seconds-of-css 仓库的 snippets 目录,每个片段均为遵循 snippet-template.md 格式的独立 Markdown 文档,由统一的 frontmatter(title、type: snippet、tags、dateModified)描述元信息。本片段标记为 tags: <a href="https://link.gitcode.com/i/5b9aa62d4550ac196f5c499047a9dc40" target="_blank">interactivity],即归入"交互性"类别;仓库的 [CONTRIBUTING.md 明确规定片段需解决真实世界问题且足够抽象、可直接套用到不同场景,这正是本片段"三段式图标 + 悬停过渡"能够被广泛复用的原因。
需要说明的适用前提:
- 效果依赖 hover 能力,在触屏设备上不存在鼠标悬停状态,需配合点击/触摸方案使用(如上述
.active类改造); transform、opacity、transition均属于现代浏览器广泛支持的 CSS 特性,无需前缀即可工作于当前主流版本;对旧浏览器(如 IE)如需支持,可考虑添加-webkit-前缀的过渡与变换声明;- 片段本身以最小实现为原则,未包含无障碍与键盘交互处理,用于生产环境时应结合
button语义与aria-expanded等属性做增强。
小结
本文完整还原了 30-seconds-of-css 中 hamburger-button.md 的核心实现:用 display: flex 与 justify-content: space-between 自动排布三条横杠,用 transform-origin: left 控制旋转轴心,用 transform: rotate(±45deg) 与 opacity: 0 在 :hover 时把汉堡图标平滑过渡为叉号,全程零 JavaScript。掌握这一组合后,你不仅能照搬该按钮,更能将"变换原点 + 过渡动画"的思路迁移到更多悬停微交互设计中。