CSS的@keyframes规则配合animation属性,可以轻松实现页面元素的动画效果。但这种声明式的写法有一个明显局限:所有关键帧在页面加载时就固定下来了,如果希望动画的终点位置、颜色或者时长根据用户的操作实时变化,纯CSS方案就力不从心了。这时候就需要借助JavaScript来动态创建和控制关键帧动画。本文将从动态注入keyframes规则、使用Web Animations API、监听动画事件三个层面,详细讲解如何用脚本完全掌控网页动效。

一、动态注入和修改keyframes规则
浏览器提供了CSSStyleSheet接口,允许我们直接操作样式表。通过document.styleSheets可以拿到当前页面所有样式表的集合,也可以先用document.createElement('style')创建一个新的样式表再插入文档。向这个样式表里插入一条@keyframes规则,就相当于在CSS文件里手写了一段动画定义,浏览器会立即应用它。
下面这段代码演示了如何根据随机参数动态生成一个关键帧动画,并将其应用到指定元素上:
function createDynamicAnimation(element) {
// 动态计算目标位置
const targetX = Math.random() * 300;
const targetY = Math.random() * 200;
// 拼接keyframes规则文本
const keyframes = `
@keyframes floatMove {
0% { transform: translate(0, 0); }
50% { transform: translate(${targetX / 2}px, ${targetY}px); }
100% { transform: translate(${targetX}px, ${targetY / 2}px); }
}
`;
// 创建新的style元素并注入文档
const style = document.createElement('style');
style.textContent = keyframes;
document.head.appendChild(style);
// 应用到目标元素
element.style.animation = 'floatMove 2s ease-in-out infinite';
}这种方式的优点是完全复用了CSS动画的渲染机制,浏览器依然会在合成器线程执行transform动画,性能表现与手写CSS完全一致。缺点是如果频繁修改动画参数,不断追加style元素会造成样式表膨胀,需要做好清理工作。更稳妥的做法是只创建一个style元素,每次通过sheet.deleteRule()删除旧规则再用sheet.insertRule()插入新规则,避免内存泄漏。
二、使用Web Animations API直接控制动画
Web Animations API(简称WAAPI)是浏览器内置的动画接口,它把CSS动画和transition的能力封装成了一套可以编程调用的JavaScript API。核心方法是元素上的animate(),它接收两个参数:第一个是关键帧数组,第二个是配置对象,用来指定时长、缓动函数、迭代次数等。与动态注入keyframes相比,WAAPI不需要操作样式表,返回的Animation对象还提供了播放、暂停、变速、反转等精细控制能力。
基本用法如下:
const box = document.querySelector('.box');
// 关键帧数组,每个元素对应一个关键帧
const animation = box.animate(
[
{ transform: 'translateX(0) scale(1)', opacity: 1 },
{ transform: 'translateX(200px) scale(1.2)', opacity: 0.6, offset: 0.7 },
{ transform: 'translateX(400px) scale(1)', opacity: 1 }
],
{
duration: 1500, // 动画时长,毫秒
easing: 'cubic-bezier(0.4, 0, 0.2, 1)',
iterations: Infinity, // 无限循环
direction: 'alternate' // 交替反向播放
}
);
// 运行时控制
animation.pause(); // 暂停
animation.play(); // 播放
animation.reverse(); // 反向播放
animation.playbackRate = 2; // 两倍速
animation.currentTime = 500; // 跳转到第500毫秒
animation.finish(); // 直接跳到结束状态其中offset属性等价于CSS关键帧中的百分比位置,取值0到1之间,不写则自动均匀分布。值得特别关注的是playbackRate属性,它可以在动画运行过程中随时调整速度,设置为负值甚至可以让动画倒放,这是纯CSS动画无法做到的。另外,animation.commitStyles()能把动画结束时的样式固化到元素的内联样式上,配合fill: 'forwards'可以避免动画结束后元素跳回初始状态的问题。
WAAPI的动画是异步的,animate()返回的对象是一个thenable,可以直接用await等待动画完成,这让串联多个动画变得非常自然:
async function playSequence() {
await box.animate(
[{ transform: 'scale(1)' }, { transform: 'scale(1.5)' }],
{ duration: 300, fill: 'forwards' }
).finished;
await box.animate(
[{ opacity: 1 }, { opacity: 0 }],
{ duration: 500, fill: 'forwards' }
).finished;
console.log('全部动画播放完成');
}三、监听动画事件与交互联动
除了创建动画,JavaScript还可以感知动画的生命周期。传统的CSS方案里有三个事件可用:animationstart在动画开始时触发,animationiteration在每次循环迭代开始时触发,animationend在动画完全结束时触发。配合事件委托,可以方便地实现在动画结束后清理元素、触发回调等逻辑:
box.addEventListener('animationend', (e) => {
if (e.animationName === 'floatMove') {
console.log('floatMove动画已结束');
box.remove(); // 动画结束后移除元素
}
});需要注意,如果动画设置了animation-iteration-count: infinite,animationend事件永远不会触发,只能依赖animationiteration来感知每次循环。使用WAAPI时则更直接,Animation对象提供了onfinish和oncancel事件处理器,语义更清晰,而且不受迭代次数的影响,调用cancel()取消动画时也能准确收到通知。
在交互场景下,一个典型的组合是:用鼠标位置作为输入,实时更新动画的目标关键帧。例如拖拽时先调用animation.pause()记录当前currentTime,松手后重新创建一个从当前位置出发、到新目标位置结束的动画,就能实现流畅的连续动画效果,而不是生硬地从起点重新播放。
四、方案对比与性能建议
三种方式各有适用场景。动态注入keyframes适合动画结构相对固定、只有少量参数需要变化的场合,它保留了CSS的声明式简洁;Web Animations API适合需要精细控制和动画编排的复杂交互,是目前的主流推荐方案;直接操作CSS属性配合requestAnimationFrame做逐帧计算则适合物理模拟类的动画,比如惯性滚动、弹簧效果。
从性能角度看,无论采用哪种方式,都应该优先动画transform和opacity这两个属性,因为它们可以完全在GPU合成器上执行,不会触发重排和重绘。要避免在关键帧中动画width、height、top、left这类会引发布局计算的属性。此外,WAAPI还支持composite: 'add'选项,可以让多个动画的效果叠加而不是相互覆盖,在组合复杂动效时非常有用。现代浏览器对WAAPI的支持已经相当完善,包括Chrome、Firefox、Safari和Edge在内的主流浏览器均已实现,可以放心在生产环境使用,只需注意低版本IE不在支持范围内,必要时做好特性检测即可。
JavaScript动画关键帧动画Web Animations API修改时间:2026-08-31 23:13:07