动画类名必须是BEM修饰符,如.modal--open;禁用.is-open等脱离块上下文的类名,须绑定具体组件、用双连字符、避免驼峰、显式声明transition/animation、禁用*{transition:all}、精准控制JS增删时机、响应式状态也走修饰符并统一断点变量。

动画类名必须是 BEM 修饰符,不能是独立状态类
直接写 .is-open 或 .fade-in 是错的——这类类名脱离块上下文,无法被工具链识别,也破坏语义隔离。BEM 要求动画状态必须绑定到具体组件,比如 .modal--open、.button--loading。它不是“加个动效”,而是“modal 这个块当前处于打开状态”。
常见错误包括:
- 在 JS 里通配添加
el.classList.add('is-open'),结果所有带该类的元素都套用同一套 CSS,不管它是不是 modal - 把
.card--fade-in写成.card-fade-in:双连字符--是解析器识别修饰符的唯一依据,漏掉就变成新 Block,选择器失效 - 用驼峰命名如
.cardFadeIn:CSS 不支持驼峰类名,IDE 类名跳转、Stylelint 规则全失效
每个修饰符必须显式声明 animation 或 transition
只加 .drawer--expanded 类,却不写 transition: max-height 0.3s ease,结果就是展开有动画、收起直接跳变。浏览器不会自动 fallback,也不会继承父级或全局的 transition。
关键点:
立即学习“前端免费学习笔记(深入)”;
-
transition必须写在基础块类(如.drawer)上,修饰符(如.drawer--expanded)只负责改值(如max-height: 500px) - 用
animation时,修饰符规则里必须显式写animation: drawer-slide-in 0.4s ease,且@keyframes名称要语义对齐 - 禁用
* { transition: all .2s }:它会让color、margin等非预期属性也过渡,且无法配合will-change优化
JS 控制类名时,时机比增删逻辑更重要
动画播不完、类名被误删、多个状态打架——这些问题几乎都出在“什么时候加、什么时候收”没理清。
实操要点:
- 入场动画:
element.classList.add('card--fade-in')→ 监听animationend→ 检查event.animationName === 'card-fade-in'→ 再清理 - 退出动画:先加
.card--fade-out,再移除.card--fade-in,确保 CSS 能触发退出过渡;不能靠animation-direction: reverse反向播放,行为不可预测 - 竞态处理:同一元素切换多个动画前,先调用
element.getAnimations().forEach(a => a.cancel())主动中断旧动画
响应式动画状态也要走修饰符,但不能混进媒体查询
响应式不是让类名变长,而是让修饰符表达“布局意图”,比如 .nav--mobile-active 或 .sidebar--collapsed。它不决定“何时生效”,只定义“当前是什么状态”。
正确做法:
- 用
matchMedia监听断点,回调中显式增删修饰符,如navEl.classList.toggle('nav--mobile-active', matches) - CSS 中只写
.nav--mobile-active { display: flex; },不混入其他非该状态逻辑 - 禁止在
@media块里写组合选择器如.nav__list--mobile .nav__link,这会破坏修饰符的独立性 - 断点数值必须统一收口到变量(如
$bp-md: 768px),不能硬编码在类名或媒体查询条件里
最易被忽略的是:动画修饰符不是“开关”,而是「块的状态投影」。挪到别的组件上就失效,换种设备就可能卡顿,甚至同一个修饰符在 SSR 和 CSR 下状态不同步——这些都不是 CSS 写得不够炫的问题,而是从类名定义那一刻起,就没把它当状态来管。


















