必须用 bem 修饰符(--)触发动画,因其被工具链识别、避免选择器失效、保障语义绑定与复用;禁用 .animate 或 [data-state] 等替代方案。

动画状态必须用 BEM 修饰符(--)触发,不能靠 JS 直接调用 animation 或靠 CSS 继承生效——否则动画不可预测、无法复用、调试时找不到源头。
为什么必须用 --modifier 而不是 .animate 或 [data-state]
工具链(Stylelint、PostCSS 插件、VS Code 类名跳转)只识别 -- 开头的类为状态类;写成 .card-fade-in 会被当成新 Block,脱离 card 上下文,CSS 选择器失效。用 data-state="fading" 看似灵活,但无法被 CSS 选择器直接匹配动画规则,还得额外写 JS 绑定样式,破坏声明式逻辑。
常见错误现象:.modal__content 加了 .is-fading 类,但 CSS 里没定义 .is-fading .modal__content,结果动画根本不动;或者多个组件共用 .is-fading,导致 A 组件动画一播,B 组件也跟着闪。
- 每个动画修饰符必须和对应
@keyframes名语义对齐:@keyframes modal-fade-out→.modal--fading-out - 禁止跨 Block 复用修饰符名:不能让
.button--fading-out和.modal--fading-out共享同一套 CSS 动画规则 - 修饰符必须挂载在能承载动画的元素上:若
transform在.carousel__track,那--sliding就得加在它身上,而不是父级.carousel
animation 属性必须显式写在修饰符规则里
只加 .card--fade-in 类,却不写 .card--fade-in .card__body { animation: card-fade-in 0.4s ease; },等于白搭。浏览器不会“自动推断”该播哪个动画。
容易踩的坑:
- 漏写
animation-fill-mode:入场动画播完后元素突然回退到初始态,是因为没设animation-fill-mode: forwards - 用
transition写复杂序列:比如“淡入→缩放→位移”,transition只能描述两态之间变化,必须用@keyframes+--modifier显式控制 - 依赖全局
* { animation: … }:会导致非预期元素也被动播放,且无法用will-change精准提示 GPU 加速
JS 控制修饰符增删的时机要点
类名加得太早或删得太快,动画根本播不完。关键不是“加不加”,而是“何时加、何时收、收多少”。
实操建议:
- 入场动画:先
el.classList.add('card--fade-in'),监听animationend事件,且必须校验event.animationName === 'card-fade-in',再清理类 - 退出动画:先加退出类(
card--fade-out),再移除入场类(card--fade-in),确保 CSS 能同时匹配两个状态做过渡 - 竞态处理:同一元素连续触发多次动画(如快速点击按钮),用
el.getAnimations().forEach(a => a.cancel())主动中断旧动画,避免队列堆积
多个动画状态共存时,修饰符不能叠加
.carousel--playing--paused 是非法写法,也不起作用。BEM 修饰符表达的是「当前主行为模式」,不是布尔开关集合。
正确做法:
- 用单一主导修饰符表示主状态:
.carousel--playing、.carousel--paused、.carousel--loading - 次要状态交由
data-属性或独立工具类承载,例如data-carousel-pause-reason="network"供 JS 判断,CSS 不参与 - 若需视觉叠加(如暂停中还在加载),拆到子元素级:
.carousel__overlay--loading和.carousel__control--paused分开控制
最易被忽略的一点:修饰符语义必须绑定 Block。把 .card--fade-in 塞进 modal 结构里,动画照样播,但语义断裂——它不再是“卡片入场”,而成了“某个叫 card 的东西在 modal 里瞎动”。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











