BEM 不管理关键帧,关键帧命名须与修饰符解耦;应采用小写连字符格式如 modal-fade-in,动画声明写在基础类中,修饰符仅控制状态,JS 需同步动画生命周期。

直接说结论:BEM 不管理关键帧(@keyframes),它只管类名结构;关键帧命名必须与 BEM 修饰符解耦,否则动画逻辑会和组件状态混作一团,最终导致 JS 控制失序、CSS 变量无法注入、构建工具误删。
为什么不能用 .modal--fade-in 绑定 @keyframes modal--fade-in
这是最常踩的语义陷阱。BEM 的 --modifier 表达的是“当前处于什么状态”,而 @keyframes 描述的是“如何随时间变化”。把两者名字强行对齐,等于让 CSS 动画承担状态判断职责——结果就是:
-
.modal--fade-in类一旦被 JS 移除,动画就中断,但 DOM 状态可能还没真正进入“已显示” - 无法复用同一套动画给不同组件(比如
toast--fade-in得重写一遍 keyframes) - Webpack 或 Vite 的 CSS tree-shaking 工具会把未出现在 HTML 中的
@keyframes当死代码删掉——而.modal--fade-in往往是 JS 动态添加的
@keyframes 名字该怎么起才符合 BEM 原则
关键帧名不参与 BEM 层级,但它必须可预测、可复用、不和修饰符冲突。推荐按“动词+宾语+上下文”小写连字符格式:
- 正确:
@keyframes modal-fade-in、@keyframes toast-slide-up、@keyframes tab-panel-fade-out - 错误:
@keyframes modal--fade-in(含双连字符,易被误认为修饰符)、@keyframes fadeIn(无上下文,全局污染风险高) - 注意:所有 keyframes 名必须全小写+连字符,避免大小写敏感问题(尤其在 Windows + Webpack 场景下)
动画触发靠修饰符,但动画定义必须写在基础类里
BEM 修饰符(如 .modal--visible)只负责“告诉 CSS:现在该动了”,真正的 animation 声明得写在常态基础类中,否则隐藏时会跳变:
立即学习“前端免费学习笔记(深入)”;
.modal__overlay {
opacity: 0;
animation: modal-fade-in 0.25s ease-out forwards;
/* 注意:这里定义动画,不是在 .modal--visible 里 */
}
.modal--visible .modal__overlay {
opacity: 1;
}- 动画属性写在基础类(
.modal__overlay)里,保证“入场”和“退场”都受控 -
.modal--visible只控制状态可见性(opacity、visibility、transform等),不写animation - 若需不同方向动画(如 slide-left / slide-right),用不同修饰符:
.modal--slide-left→ 触发modal-slide-left关键帧
JS 控制动画流程时,修饰符添加/移除的时机很关键
动画不是“加个类就完事”,BEM 结构要求状态类必须与动画生命周期对齐:
- 入场动画开始前,先加
.modal--visible,再等requestAnimationFrame后加.modal__overlay--entering(用于触发动画) - 退场时,先移除
.modal--visible,再监听animationend事件后彻底清理 DOM —— 否则.modal--visible被提前移除会导致动画中断 - 不要用
el.classList.toggle('modal--visible')直接切换,因为没考虑动画是否已完成;应封装成showModal()/hideModal()方法,内部处理状态队列
真正难的不是写对类名,而是让 JS 状态机和 CSS 动画帧严格同步——一旦错开一帧,遮罩就卡住、内容就闪退、forwards 就失效。这没法靠 BEM 自动解决,得靠每个动画组件自己校准 timing 和 class 操作节奏。


















