CSS 与 JS:动画效果哪家强,两者分析见真章

108 阅读6分钟

如果让你用代码写一个动画效果,你第一想到的是什么,使用css还是js?

大多数开发者可能会下意识地打开 css 文件,毕竟,用 transition@keyframes 写个滑动效果,代码简洁又直观。

但此时,你又觉得用js 写动画,代码似乎“更加可控”一些,比如能够随时暂停、调整速度,甚至根据用户行为动态变化。

那么,真相究竟是什么呢,哪种语言写动画更好用呢?

下面,我将用 js和css 写两个动画效果作为案例,对两种方法的优缺点进行分析。


一、先说结论

1. CSS:简单粗暴,性能王者

优点

  • 无需手动控制帧率

    当用户悬停 .box 时,浏览器会自动计算从 0px100px 的中间值,逐帧渲染。而开发者只需要定义起点和终点,这样可以省去复杂的插值计算。

  • 触发 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>

最后要达成的效果:当鼠标悬停在这个粉色方块上时,粉色方块会向右移动,鼠标移开,方块回归原位。

aerph-09ee8.gif

CSS 实现

CSS 中,如果要实现一个动画效果,最常用的就是通过 transitiontransform@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);
});

代码分析

  1. 事件绑定

    • 使用 mouseenter 和 mouseleave 事件,模拟 CSS 的 :hover 行为。
    • 当鼠标进入 .box 时,调用 animate(distance);当鼠标离开时,调用 animate(0)
  2. 使用 transform 替代 left

    • 通过 transform: translateX(...) 修改元素位置,避免触发重排。
    • 初始位置默认为 0,即 translateX(0px)
  3. 模拟 CSS transition 过渡效果

    • 缓动函数:使用 ease 函数(与 CSS 的 ease 相同)实现平滑动画。
    • 动态计算帧值:通过 requestAnimationFrame 控制帧率,逐帧计算 translateX 的值。
    • 插值计算:根据时间流逝比例 progress,计算当前帧的 currentX 值。
  4. 性能优化

    • 避免重排:始终使用 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>

要实现的效果如下:

l4zom-vptpk.gif

效果
一个进度条从 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);
});

代码解析与优点

  1. 动态控制动画状态

    • 暂停/继续:通过 isPaused 标志和 requestAnimationFrame 的灵活控制,用户可随时暂停或恢复动画。
    • 加速/减速:通过调整 speed 参数(如 2x 或 0.5x),动态改变动画的播放速度,而无需重新定义动画逻辑。
  2. 实时用户交互

    • 重置动画:用户点击“重置”按钮后,进度条立即回到初始状态,并重新开始动画。这种交互在 CSS 中难以实现(需通过 JS 动态修改类名或样式)。
  3. 复杂逻辑支持

    • 时间偏移计算:在加速/减速时,通过 startTime = performance.now() - ... 计算时间偏移量,确保动画进度与当前状态一致。
    • 多状态管理:通过 isPausedspeed 等变量,灵活管理动画的多种状态。
  4. 兼容性与扩展性

    • 兼容旧浏览器:通过 requestAnimationFrame 和 performance.now(),可在现代浏览器中实现高性能动画,同时可通过 polyfill 支持旧版浏览器。
    • 扩展性强:可轻松添加更多交互(如拖动进度条、动态修改目标进度等)。

CSS vs JS 动画的选择

场景推荐方式原因
简单的属性变化(如颜色、透明度)CSS 动画代码简洁,性能最优,无需手动控制帧率。
复杂路径或动态参数(如进度条)JS 动画需要实时计算参数,动态调整动画逻辑。
需要兼容旧浏览器JS 动画虽然 CSS 动画兼容性较好,但某些旧浏览器(如 IE9 以下)不支持现代属性。
性能敏感场景(如游戏动画)JS 动画虽然性能略差,但通过 transform 和 requestAnimationFrame 可优化到接近 CSS 动画水平。

最终建议

  • 优先用 CSS 动画:适合大多数 UI 动画(如按钮悬停、加载提示)。

  • 不得已用 JS 动画:需要动态控制时(如数据驱动的动画、游戏逻辑)。

  • 关键优化点

    • 避免使用 lefttopwidth 等触发重排的属性,改用 transform
    • 使用 requestAnimationFrame 替代 setTimeout,确保动画与屏幕刷新率同步。

结语

文章为作者本人理解,如果发现内容有误,欢迎各位读者在评论区指正。

最后,创作不易,如果觉得这篇文章对你有所帮助,不妨动动手指,点赞 + 收藏 一波!🌟