本文详解解决 ::before 和 ::after 伪元素旋转时偏离中心的问题,通过重构 DOM 结构、合理使用 transform-origin、inset-inline 和 translate 配合 rotate,实现三横线精准绕中心旋转成 X 形的汉堡菜单动画。
本文详解解决 `::before` 和 `::after` 伪元素旋转时偏离中心的问题,通过重构 dom 结构、合理使用 `transform-origin`、`inset-inline` 和 `translate` 配合 `rotate`,实现三横线精准绕中心旋转成 x 形的汉堡菜单动画。
在 CSS 动画中,::before 和 ::after 伪元素默认以自身盒模型中心为旋转原点(transform-origin: center),但若其父容器未明确设置定位上下文或尺寸基准,或 transform 中混用 translate 与 rotate 且过渡时序不一致,极易导致视觉上“旋转偏移”——看似没绕中心转,实则是位移与旋转的叠加效果未对齐参考系。
核心问题在于原始代码中:
- #menu-icon 本身无宽高内容,仅靠伪元素撑开,但其 position: absolute 缺少 top/left 定位,导致 transform-origin: center 失效(绝对定位元素的 center 基于其生成的盒,而空元素盒尺寸为 0);
- ::before/::after 的 translateY() 偏移量(如 var(--margin-top))在动画过程中被 translate 过渡覆盖,但初始 transform 已含 translateY,而 .animate 状态下又设 translate: 0px var(--margin-bottom),造成坐标系混乱;
- transition 属性拆分写法(如 translate 和 rotate 分开声明)易因顺序/延迟冲突引发渲染错位。
✅ 推荐解法:精简结构 + 统一参考系 + 原生比例控制
我们改用单个
:root {
--btn-size: 20vmin; /* 更简洁的响应式尺寸 */
--animation: 1s;
--anim-half: calc(var(--animation) / 2);
--anim-func: ease-in-out;
--btn-color: #929292;
}
#menu-btn {
height: var(--btn-size);
aspect-ratio: 1; /* 替代手动设 width,保证正方形 */
background: linear-gradient(var(--btn-color) 0 0)
50% / 100% 20% no-repeat; /* 中间横线:居中、宽100%、高20% */
border: none;
cursor: pointer;
position: relative;
transition:
rotate var(--animation) var(--anim-func),
background-size 0s var(--anim-half); /* 背景横线淡出需零延迟启动 */
}
#menu-btn::before,
#menu-btn::after {
content: "";
position: absolute;
inset-inline: 0; /* 等价于 left: 0; right: 0 */
height: 20%; /* 与中间线等高,视觉统一 */
background-color: var(--btn-color);
transition:
translate var(--anim-half) var(--anim-func) var(--anim-half),
rotate var(--anim-half) var(--anim-func);
}
#menu-btn::before { top: 0; }
#menu-btn::after { bottom: 0; }
/* 激活态:整体旋转 + 上下线位移+旋转 */
#menu-btn.animate {
rotate: 360deg;
background-size: 0% 20%; /* 中间线宽度缩为0,实现“消失” */
}
#menu-btn.animate::before,
#menu-btn.animate::after {
transition:
translate var(--anim-half) var(--anim-func),
rotate var(--anim-half) var(--anim-func) var(--anim-half);
}
#menu-btn.animate::before {
rotate: 45deg;
translate: 0 calc(2 * var(--btn-size) / 5); /* 向下移动 40% 按钮高度 */
}
#menu-btn.animate::after {
rotate: -45deg;
translate: 0 calc(-2 * var(--btn-size) / 5); /* 向上移动 40% 按钮高度 */
}
<button id="menu-btn" onclick="toggleState(this)"></button>
? 关键优化说明:
- ✅ aspect-ratio: 1 替代 min(8vh, 8vw),语义更清晰且兼容性良好(现代浏览器均支持);
- ✅ inset-inline: 0 确保伪元素水平铺满父容器,消除因 width 未显式设置导致的宽度计算偏差;
- ✅ translate 使用 calc() 直接基于 --btn-size 计算位移量,避免依赖 --bar-height 等中间变量,提升可维护性;
- ✅ background-size 过渡延迟设为 0s,确保中间横线在动画起始瞬间开始收缩,与上下线动作同步;
- ✅ 所有 transition 属性合并书写,避免浏览器解析歧义,rotate 与 translate 的时序由 transition-delay 精确控制。
⚠️ 注意事项:
- 若需支持旧版 Safari(
- translate 的 calc() 表达式中 2 * var(--btn-size) / 5 对应原设计中 --bar-height * 2 的视觉距离(因 --bar-height = 20% × --btn-size),确保三线最终交汇于中心;
- 动画结束后,.animate 类可配合 JS 移除以重置状态,或保留用于样式判断。
此方案不仅彻底解决旋转偏心问题,还大幅降低 CSS 复杂度,提升可读性与可扩展性——真正的“少即是多”。










