
通过为展开和收拢分别定义独立的动画(而非依赖 animation-direction: reverse),配合 javascript 精确控制类名切换,可实现流畅、可控的双向 css 动画效果。
在 Web 开发中,仅靠 animation-direction: reverse 实现“反向动画”常会失效——因为该属性仅改变已定义关键帧的播放顺序,不会自动重置元素的最终状态。例如,当 .MenuOpen 动画执行完毕后,元素停留在 height: 100px; width: 300px,此时若直接添加 .MenuClose 并设置 animation-direction: reverse,浏览器仍会从 from(即 height: 0px)开始反向播放,导致视觉错乱或动画不触发。
✅ 正确做法是:为打开和关闭分别定义语义清晰、方向明确的独立动画,并确保动画结束时样式与对应状态一致。
以下是优化后的完整实现:
#MenuContainer {
height: 0;
width: 0;
left: 10px;
top: 20px;
padding: 5px 0 0 0;
position: relative;
overflow: hidden;
background-color: #3388ff;
/* 初始状态需与 MenuOpen 的 'from' 完全一致 */
}
.MenuOpen {
animation: MenuOpen 0.4s ease-out forwards;
}
.MenuClose {
animation: MenuClose 0.3s ease-in forwards;
}
@keyframes MenuOpen {
from {
height: 0;
width: 0;
opacity: 0;
}
to {
height: 100px;
width: 300px;
opacity: 1;
}
}
@keyframes MenuClose {
from {
height: 100px;
width: 300px;
opacity: 1;
}
to {
height: 0;
width: 0;
opacity: 0;
}
}function showMenu() {
const el = document.getElementById("MenuContainer");
if (el.classList.contains('MenuOpen')) {
// 正在打开 → 触发关闭动画
el.classList.remove('MenuOpen');
el.classList.add('MenuClose');
// 动画结束后清理类名,避免干扰下次操作
el.addEventListener('animationend', () => {
el.classList.remove('MenuClose');
}, { once: true });
} else {
// 当前关闭 → 触发打开动画
el.classList.remove('MenuClose');
el.classList.add('MenuOpen');
}
}? 关键注意事项:
立即学习“前端免费学习笔记(深入)”;
- ✅ 动画 forwards 填充模式必须启用,确保动画结束后保留终态样式;
- ✅ .MenuClose 的 from 必须严格匹配 .MenuOpen 的 to(即当前实际尺寸),否则会出现跳变;
- ✅ 使用 animationend 事件清理 .MenuClose 类,防止重复点击导致动画中断或堆积;
- ⚠️ 避免同时存在 .MenuOpen 和 .MenuClose ——它们应互斥,由 JS 逻辑严格管理;
- ? 可进一步封装为自定义 Hook(React)或 Web Component,提升复用性。
这种“双动画 + 状态驱动”的方案,不仅解决了反向动画不可靠的问题,还赋予开发者对时长、缓动、触发时机的完全控制权,是生产环境推荐的最佳实践。










