如果让你用代码写一个动画效果,你第一想到的是什么,使用css还是js?
大多数开发者可能会下意识地打开 css 文件,毕竟,用
transition或@keyframes写个滑动效果,代码简洁又直观。但此时,你又觉得用js 写动画,代码似乎“更加可控”一些,比如能够随时暂停、调整速度,甚至根据用户行为动态变化。
那么,真相究竟是什么呢,哪种语言写动画更好用呢?
下面,我将用 js和css 写两个动画效果作为案例,对两种方法的优缺点进行分析。
一、先说结论
1. CSS:简单粗暴,性能王者
优点:
-
无需手动控制帧率:
当用户悬停
.box时,浏览器会自动计算从0px到100px的中间值,逐帧渲染。而开发者只需要定义起点和终点,这样可以省去复杂的插值计算。 -
触发 GPU 加速:
GPU,全称图形处理器, GPU 的并行计算能力更强,能更高效地完成像素级操作,所以,在使用
transform(如translateX)和opacity时,浏览器会将这些属性的变化交给 GPU 处理,而不是 CPU。 -
性能更优,避免重排 :
如果使用
left: 100px实现动画,每次修改left属性,都会触发浏览器重新计算布局(Reflow),而transform会创建一个独立图层,修改时只需更新该图层,无需触发布局计算。
2. JS :灵活强大,累死牛马
-
灵活性和控制性:
使用 JavaScript 实现动画,可以提供更高级的控制能力。例如,可以根据用户的输入、数据的变化或其它事件来动态调整动画的状态。而 CSS 动画通常依赖于预定义的关键帧和固定的持续时间,缺乏这种灵活性。
-
复杂的动画逻辑:
对于复杂的动画逻辑,如物理仿真、碰撞检测等,JavaScript 提供了更强的支持。通过 JavaScript,开发者可以编写算法来模拟现实世界中的物理行为,这是仅使用 CSS 难以实现的。
-
交互性和响应性:
JavaScript 能够监听用户的各种输入(如鼠标移动、点击、触摸等),并基于这些输入实时更新动画状态,从而创建出高度交互性的体验。虽然 CSS 也支持某些交互状态(如
:hover),但在处理复杂交互时显得力不从心。 -
跨浏览器兼容性:
尽管现代浏览器对 CSS 动画的支持已经相当不错,但是在一些老版本浏览器中可能存在兼容性问题。而使用 JavaScript 则可以通过各种库(如 GSAP)或者 polyfills 来确保在不同浏览器环境下的兼容性。
-
性能监控与优化:
在 JavaScript 中,你可以更直接地管理动画的性能,比如使用
requestAnimationFrame来同步动画与显示器刷新率,避免不必要的重绘和回流。这对于高频率更新的动画尤其重要。 -
集成与扩展性:
JavaScript 动画更容易与其他 JavaScript 代码集成,并且可以方便地与后端服务进行通信,根据服务器返回的数据动态改变动画的行为。此外,还可以轻松地将多个动画组合在一起,创建复杂的序列动画。
二、示例动画1
基础样式:
<style>
.box {
width: 100px;
height: 50px;
background-color: pink;
}
</style>
<div class="box"></div>
最后要达成的效果:当鼠标悬停在这个粉色方块上时,粉色方块会向右移动,鼠标移开,方块回归原位。
CSS 实现
CSS 中,如果要实现一个动画效果,最常用的就是通过 transition、transform 或 @keyframes 实现,它们写起来就像“声明目标状态”,浏览器会自动处理中间值。
实现代码如下:
.box {
width: 100px;
height: 50px;
background-color: pink;
transition: transform 0.5s ease;
}
.box:hover {
transform: translateX(100px);
}
2. JS 实现
对于实现和上面相同的效果,js代码如下:
onst box = document.querySelector('.box');
// 定义动画参数
const duration = 500; // 动画时长(毫秒)
const distance = 100; // 移动距离(像素)
// 模拟 CSS transition 的缓动函数(ease)
function ease(t) {
return t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2;
}
// 动画核心函数
function animate(targetX, callback) {
const startTime = performance.now();
const startX = parseFloat(box.style.transform?.match(/translateX\(([^)]+)/)?.[1] || 0);
function step(currentTime) {
const elapsed = currentTime - startTime;
const progress = Math.min(elapsed / duration, 1);
const easedProgress = ease(progress);
const currentX = startX + (targetX - startX) * easedProgress;
box.style.transform = `translateX(${currentX}px)`;
if (progress < 1) {
requestAnimationFrame(step);
} else if (callback) {
callback();
}
}
requestAnimationFrame(step);
}
// 鼠标进入时执行动画
box.addEventListener('mouseenter', () => {
animate(distance);
});
// 鼠标离开时恢复位置
box.addEventListener('mouseleave', () => {
animate(0);
});
代码分析
-
事件绑定
- 使用
mouseenter和mouseleave事件,模拟 CSS 的:hover行为。 - 当鼠标进入
.box时,调用animate(distance);当鼠标离开时,调用animate(0)。
- 使用
-
使用
transform替代left- 通过
transform: translateX(...)修改元素位置,避免触发重排。 - 初始位置默认为
0,即translateX(0px)。
- 通过
-
模拟 CSS
transition过渡效果- 缓动函数:使用
ease函数(与 CSS 的ease相同)实现平滑动画。 - 动态计算帧值:通过
requestAnimationFrame控制帧率,逐帧计算translateX的值。 - 插值计算:根据时间流逝比例
progress,计算当前帧的currentX值。
- 缓动函数:使用
-
性能优化
- 避免重排:始终使用
transform修改位置。 - 精准控制动画:通过
performance.now()获取高精度时间戳,确保动画与屏幕刷新率同步。
- 避免重排:始终使用
可以看到,JavaScript 动画依赖 requestAnimationFrame(简称 rAF),需要手动控制每一帧的逻辑。
到这里,看起来似乎是css更胜一筹,然而!!!,事实真是如此吗?
三、示例动画2
基础样式
<style>
.progress-container {
width: 300px;
height: 20px;
background-color: #eee;
border-radius: 10px;
overflow: hidden;
}
.progress-bar {
width: 0%;
height: 100%;
background-color: #4caf50;
transition: none;
}
</style>
<div class="progress-container">
<div class="progress-bar" id="progressBar"></div>
</div>
<button id="toggleBtn">暂停</button>
<button id="speedBtn">2x</button>
<button id="resetBtn">重置</button>
要实现的效果如下:
效果:
一个进度条从 0% 到 100% 的填充动画,用户可以通过按钮动态控制:
- 暂停/继续:点击按钮可随时暂停或恢复动画。
- 加速/减速:点击按钮可调整动画速度(如 2x 或 0.5x)。
- 重置:点击按钮可将进度条重置为初始状态。
1. CSS 实现
别看了,CSS 动画无法直接实现,原因如下:
- 暂停/继续:CSS 动画只能通过
animation-play-state暂停,但无法动态恢复到当前进度。 - 加速/减速:CSS 动画的
animation-duration固定,无法动态调整。 - 实时重置:CSS 动画无法根据用户操作立即重置到初始状态。
2. JS 实现
const progressBar = document.getElementById('progressBar');
const toggleBtn = document.getElementById('toggleBtn');
const speedBtn = document.getElementById('speedBtn');
const resetBtn = document.getElementById('resetBtn');
let isPaused = false;
let speed = 1; // 默认速度
let progress = 0;
let animationId = null;
// 动画核心函数
function animate() {
if (isPaused) return;
const now = performance.now();
const duration = 2000; // 基础动画时长(毫秒)
const targetProgress = 100;
const elapsed = (now - startTime) * speed; // 根据速度调整时间流逝
const progress = Math.min(elapsed / duration, 1);
const currentWidth = progress * targetProgress;
progressBar.style.width = `${currentWidth}%`;
if (progress < 1) {
animationId = requestAnimationFrame(animate);
}
}
// 初始化动画
let startTime = performance.now();
animationId = requestAnimationFrame(animate);
// 暂停/继续按钮
toggleBtn.addEventListener('click', () => {
isPaused = !isPaused;
toggleBtn.textContent = isPaused ? '继续' : '暂停';
if (!isPaused) {
startTime = performance.now() - (progressBar.offsetWidth / 100) * 2000 / speed;
animationId = requestAnimationFrame(animate);
}
});
// 加速/减速按钮
speedBtn.addEventListener('click', () => {
speed = speed === 1 ? 2 : 0.5;
speedBtn.textContent = `${speed}x`;
});
// 重置按钮
resetBtn.addEventListener('click', () => {
isPaused = false;
speed = 1;
progressBar.style.width = '0%';
startTime = performance.now();
animationId = requestAnimationFrame(animate);
});
代码解析与优点
-
动态控制动画状态
- 暂停/继续:通过
isPaused标志和requestAnimationFrame的灵活控制,用户可随时暂停或恢复动画。 - 加速/减速:通过调整
speed参数(如2x或0.5x),动态改变动画的播放速度,而无需重新定义动画逻辑。
- 暂停/继续:通过
-
实时用户交互
- 重置动画:用户点击“重置”按钮后,进度条立即回到初始状态,并重新开始动画。这种交互在 CSS 中难以实现(需通过 JS 动态修改类名或样式)。
-
复杂逻辑支持
- 时间偏移计算:在加速/减速时,通过
startTime = performance.now() - ...计算时间偏移量,确保动画进度与当前状态一致。 - 多状态管理:通过
isPaused、speed等变量,灵活管理动画的多种状态。
- 时间偏移计算:在加速/减速时,通过
-
兼容性与扩展性
- 兼容旧浏览器:通过
requestAnimationFrame和performance.now(),可在现代浏览器中实现高性能动画,同时可通过 polyfill 支持旧版浏览器。 - 扩展性强:可轻松添加更多交互(如拖动进度条、动态修改目标进度等)。
- 兼容旧浏览器:通过
CSS vs JS 动画的选择
| 场景 | 推荐方式 | 原因 |
|---|---|---|
| 简单的属性变化(如颜色、透明度) | CSS 动画 | 代码简洁,性能最优,无需手动控制帧率。 |
| 复杂路径或动态参数(如进度条) | JS 动画 | 需要实时计算参数,动态调整动画逻辑。 |
| 需要兼容旧浏览器 | JS 动画 | 虽然 CSS 动画兼容性较好,但某些旧浏览器(如 IE9 以下)不支持现代属性。 |
| 性能敏感场景(如游戏动画) | JS 动画 | 虽然性能略差,但通过 transform 和 requestAnimationFrame 可优化到接近 CSS 动画水平。 |
最终建议:
-
优先用 CSS 动画:适合大多数 UI 动画(如按钮悬停、加载提示)。
-
不得已用 JS 动画:需要动态控制时(如数据驱动的动画、游戏逻辑)。
-
关键优化点:
- 避免使用
left、top、width等触发重排的属性,改用transform。 - 使用
requestAnimationFrame替代setTimeout,确保动画与屏幕刷新率同步。
- 避免使用
结语
文章为作者本人理解,如果发现内容有误,欢迎各位读者在评论区指正。
最后,创作不易,如果觉得这篇文章对你有所帮助,不妨动动手指,点赞 + 收藏 一波!🌟