首页
/ animate.css 4.x JavaScript 集成指南:动态添加动画、监听 animationend 与 Promise 封装实战

animate.css 4.x JavaScript 集成指南:动态添加动画、监听 animationend 与 Promise 封装实战

2026-09-03 18:50:02作者:房伟宁

animate.css 是一款跨浏览器的纯 CSS 动画库,除了直接在 HTML 中写 class 之外,配合 JavaScript 还能实现动态触发动画、在动画结束后执行后续逻辑、以及运行时实时调节动画时长。本文基于仓库文档 Usage with Javascript 展开,并结合 source/_base.csssource/_vars.csspostcss.config.js 的源码实现,帮你理解每一行 JS 背后 CSS 层的机制,从而写出可复用、可等待、可变速的动画控制代码。

前置:animate.css 的动画类命名机制

在看 JS 用法之前,先明确一个前提:动画类由“基础类 + 动画名类”两部分组成,且都带 animate__ 前缀。源码 source/animate.css 只是入口,它通过 @import 聚合各分类目录(如 bouncing_exits/bounceOut.cssfading_entrances/fadeIn.css 等)下的动画定义;而 animate__ 前缀并不是手写的,而是构建时由 postcss.config.js 中的 postcss-prefixer 插件统一加上的,前缀值来自 package.json 里的 animateConfig.prefix 字段(值为 animate__)。因此 JS 中拼接的类名必须与这个前缀保持一致。

注意一个细节:postcss.config.jsprefix 的取值为 ctx.env === 'compat' ? '' : animateConfig.prefix——即 animate.compat.css 兼容构建不带前缀,只有 animate.css / animate.min.css 才使用 animate__ 前缀。如果你使用 compat 版本,下面的前缀逻辑需要相应调整。

动态添加动画类

最简单的用法是通过 classList 为元素同时加上基础类和动画类。以一个 bounceOutLeft 出屏动画为例(该类对应源码文件 source/bouncing_exits/bounceOutLeft.css):

const element = document.querySelector('.my-element');
element.classList.add('animate__animated', 'animate__bounceOutLeft');

为什么必须加 animate__animated 这个基础类?查看编译产物 animate.css(源码为 source/_base.css)可以看到,它承担了两个关键职责:

.animated {
  animation-duration: var(--animate-duration);
  animation-fill-mode: both;
}
  • animation-duration 从 CSS 变量读取,这是后文“运行时改时长”能生效的根基;
  • animation-fill-mode: both 保证动画结束后的最终状态(例如淡出到透明、位移后的位置)会保留在元素上,这正是各类 Out 动画“退出即消失”效果的来源。

而每个具体动画类只负责声明 animation-name 和时长比例。以 source/bouncing_exits/bounceOut.css 为例:

.bounceOut {
  animation-duration: calc(var(--animate-duration) * 0.75);
  animation-name: bounceOut;
}

也就是说,每个动画的实际时长都是 --animate-duration 的一个倍数(bounceOut 为 0.75 倍)。这种“统一变量 + calc() 比例”的设计,让全局或局部改变一个变量值,就能成比例地缩放所有动画时长。

监听 animationend 检测动画结束

CSS 动画本身不会通知 JS “我已经播完了”。animate.css 依赖标准的 animationend DOM 事件解决这个问题:

const element = document.querySelector('.my-element');
element.classList.add('animate__animated', 'animate__bounceOutLeft');

element.addEventListener('animationend', () => {
  // do something
});

animation-name 对应的关键帧执行完毕,浏览器会在该元素上触发 animationend,此时就可以执行移除元素、发送请求等后续操作。

使用这个事件时有两个值得注意的点:

  1. 元素必须真正处于动画状态。若元素没有同时具备 animate__animated 和动画类,animationend 不会触发;同理,若动画类被移除(见下文封装),要重新触发事件需重新 classList.add
  2. 减少动画偏好会让事件“瞬间”触发source/_base.css 在打印媒体和 prefers-reduced-motion: reduce 场景下把 animation-duration 强制为 1ms,因此依赖 animationend 的后续逻辑会几乎立即执行——这对可访问性是正确的行为,但编写异步流程时要有这个预期。

通过 CSS 变量在运行时调整时长

animate.css 4.x 起使用 CSS 自定义属性统一定义动画的时长、延迟与重复次数,默认值定义在 source/_vars.css

:root {
  --animate-duration: 1s;
  --animate-delay: 1s;
  --animate-repeat: 1;
}

这意味着 JS 一行代码就能改变某个元素(甚至全站)的动画速度,无需重写任何 keyframes:

const element = document.querySelector('.my-element');
element.style.setProperty('--animate-duration', '0.5s');

由于变量可以设置在不同层级,作用域也不同:

// 全局:所有动画耗时减半
document.documentElement.style.setProperty('--animate-duration', '.5s');

// 全局:所有动画耗时翻倍
document.documentElement.style.setProperty('--animate-duration', '2s');
  • 设置在 document.documentElement 上即等效于覆盖 :root 的默认值,影响页面内所有动画;
  • 直接设置到具体元素(element.style)则只影响该元素,利用 CSS 变量沿 DOM 树继承的特性实现“局部变速”;
  • 由于每个动画类都用 calc(var(--animate-duration) * 比例) 定义时长(如 bounceOut 的 0.75 倍),改变变量值时各动画会保持相对比例,不会失真。

封装 Promise 化的 animateCSS 辅助函数

手动“加类名 + 监听 animationend”的组合略显繁琐,文档给出了一个可复用的 Promise 封装函数。完整代码(来自 04-javascript.md):

const animateCSS = (element, animation, prefix = 'animate__') =>
  // We create a Promise and return it
  new Promise((resolve, reject) => {
    const animationName = `${prefix}${animation}`;
    const node = document.querySelector(element);

    node.classList.add(`${prefix}animated`, animationName);

    // When the animation ends, we clean the classes and resolve the Promise
    function handleAnimationEnd(event) {
      event.stopPropagation();
      node.classList.remove(`${prefix}animated`, animationName);
      resolve('Animation ended');
    }

    node.addEventListener('animationend', handleAnimationEnd, {once: true});
  });

逐段拆解它的实现要点:

  • 参数设计element 接收 CSS 选择器字符串(内部用 document.querySelector 解析),animation 只传动画名(如 'bounce'),prefix 默认 'animate__',与 package.jsonanimateConfig.prefix 保持一致;
  • Promise 生命周期new Promise 立即返回,resolve('Animation ended') 在动画结束时调用,因此调用方可以用 then / await 精确地“等待动画播完”;
  • {once: true}:监听器在首次 animationend 后自动移除,无需手动 removeEventListener,避免了重复执行清理逻辑的隐患;
  • event.stopPropagation():防止该事件继续冒泡到父元素——这在动画元素嵌套(比如一个卡片里还有子元素动画)时很关键,能避免父级的 animationend 监听器被误触发;
  • 清理类名handleAnimationEnd 中同时移除了 animate__animated 和动画类。这是刻意为之——保留动画类会让元素停留在 keyframes 的终态(因为 fill-mode: both),也可能阻塞下次触发;清理后元素恢复初始样式,随时可以再次调用。

调用方式有两种——不关心结束时机时直接调用,需要串行逻辑时链式处理:

animateCSS('.my-element', 'bounce');

// or
animateCSS('.my-element', 'bounce').then((message) => {
  // Do something after the animation
});

配合 async/await 甚至可以编排动画序列:

await animateCSS('.step-1', 'fadeInUp');
await animateCSS('.step-1', 'fadeOutUp');
await animateCSS('.step-2', 'fadeInUp');

实战要点与注意事项

  1. 前缀必须与构建产物匹配。本文所有示例均针对标准构建(animate.css / animate.min.css)。从 postcss.config.js 可以看到 compat 构建的前缀为空字符串,若你加载的是 animate.compat.css,需将 prefix 改为 ''
  2. animationend 是同步契约的前提。Promise 封装只在 animationend 触发时 resolve。若元素被 display: none、不在文档流中,或被媒体查询禁用动画,事件可能迟迟不触发,建议根据业务需要增加超时兜底。
  3. 变量作用域决定影响范围--animate-duration 设置在 :root(或 documentElement)是全局变速,设置在单个元素上仅局部生效;同理 --animate-delay 控制 animate__delay-* 延迟类、--animate-repeat 控制 animate__repeat-* 重复类的基准值,三者都定义在 source/_vars.css,均可用同一套 setProperty 手段在运行时调整。
  4. 减少动画偏好的兼容性代价是速度prefers-reduced-motion 下所有动画被压缩到 1ms(见 source/_base.css),基于 animationend 的串行流程会快速走完,这是符合无障碍预期的正确表现,但交互状态机(如轮播、弹窗)应确保快速跳帧后状态依然一致。
  5. 动画名以源码目录为准。可用的 animation 参数名即 source/animate.css 中聚合的各分类文件对应的动画(去掉前缀),例如 bouncefadeInUpzoomOutflipInX 等,目录结构(attention_seekers/fading_entrances/zooming_exits/ 等)就是现成的动画名清单。

小结

animate.css 与 JavaScript 的协作模型非常清晰:JS 负责“何时加类、何时移除类、如何响应事件”,CSS 变量负责“快慢与比例”,而 animationend 事件把两者缝合成异步可编排的流程。掌握了 classList 动态加类、animationend 监听、--animate-duration 变量三件套,以及一个 Promise 封装函数,就足以覆盖绝大多数“动画驱动界面状态”的实战场景;同时结合仓库中 source/_base.csssource/_vars.css 的实现,能准确预判动画在减少动画偏好、compat 构建等边界场景下的行为。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.13 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
897
5.8 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
529
593
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
915
1.83 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.58 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.35 K
1.46 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.01 K
515
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
547
388