搞懂JS动画

1,144 阅读5分钟

动画是指由许多帧静止的画面,以一定的速度(如每秒16张)连续播放时,肉眼因视觉残象产生错觉,而误以为画面活动的作品。

我们看的视频、电影等影视作品,都是某种动画,一般像电影是1秒24张画面。

什么是JS动画

从某种角度,浏览器就是一个动画播放器、一个画布,以每秒60帧的速度“播放”,当然和视频、电影还是不同,浏览器是动态的(实时渲染)计算每一帧的画面,而视频、电影是静态的(预渲染)

所谓的JS动画,就是通过JS代码来动态更改每一帧浏览器中元素的位置、颜色等属性,然后浏览器完成渲染绘制的工作,从而达到像在播放动画的效果。

JS动画比起CSS动画的最大好处就是可以有更好的控制度。

如何创作JS动画

基本循环

在浏览器中,我们需要利用 requestAnimationFrame 方法

window.requestAnimationFrame() 告诉浏览器——你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行。 - MDN

通过使用在raf中再次调用raf方法达到动画循环的效果,下面代码片段演示了一个最简单的动画循环骨架。

function step(timestamp) {
  /**
   * 这里可以添加一些改变元素的位置、颜色等属性,从而触发浏览器重新渲染的逻辑
   */

  // 继续循环,下一帧
  window.requestAnimationFrame(step);
}

window.requestAnimationFrame(step)

动画逻辑

在每一帧中,按照某种逻辑规律来变更元素的属性,这个逻规律(算法)就是动画的控制逻辑

例如,把一个元素2s内线性右移 200px的距离,那么可以很容易计算出当前右移的距离. (200 / 2000 ) * 已持续的时间

下面是匀速移动动画的简单演示代码:

let start = null

function step(timestamp) {
  if (!start) start = timestamp
  const element = document.getElementId('some-element');
  // 动画已经执行的时间
  const progress = timestamp - start;
  // 播放到当前时间的值。 这里是一个线性的过程
  const progressValue = (200 / 2000) * progress
  // 向右移动到当前播放的位置
  element.style.transform = `translateX(${Math.min(progressValue, 200)}px)`;

  // 继续循环,下一帧。超过2s结束动画播放
  if (progress < 2000) window.requestAnimationFrame(step);
}

window.requestAnimationFrame(step)

https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/85e6db879bf74a70b87d55df83b586e9~tplv-k3u1fbpfcp-zoom-1.image

除了匀速运动,我们也可以添加缓动效果以达到更好的运动效果,缓动效果可以通过一个缓动函数(easing function) 来实现。

所谓的缓动函数就是在一定的时间内控制参数的变化率 (Easing functions specify the rate of change of a parameter over time.)

让我们添加一个代码实例,实现一个简单的缓动效果

// 播放到当前时间的值。 这里是一个线性的过程
  const progressValue = (200 / 2000) * progress

  // 替换为下面。先慢后快的缓动效果
  const elapsed = progress / duration
  const easingCirc = 1 - Math.sqrt(1 - elapsed * elapsed)
  const progressValue = (200 / 2000) * progress * easingCirc

https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/2e28057d024e4868a84433d043069af5~tplv-k3u1fbpfcp-zoom-1.image

通过缓动方程,可以改变动画的变更速率,从而实现各种缓动效果,在视觉体验上使得动画更加多变和有质感。

下面列几个简单的缓动函数

  • 正弦:
t => 1 - Math.cos((t * Math.PI) / 2)
  • 圆形:
t => 1 - Math.sqrt(1 - t * t)
  • 后退:
t => t * t * (3 * t - 2)

具体可以查看下面网址,查看各种基础的缓动效果:easings.net/en

缓动效果也不仅限于上面网站中列举的,任何人都可以定义各种简单复杂的缓动函数,只要知道了这个套路,那么只要满足视觉需求,那么缓动函数可以是各种各样的🤔。还有一些较复杂的函数实现,比如:

  • 弹簧(spring): webkit.org/demos/sprin… (这是webkit的弹框效果的例子)
  • 贝塞尔(bezier):github.com/gre/bezier-… (贝塞尔缓动,这个可以模拟几乎任何的基础缓动效果,上面的一些例子本质就是一些曲线运动,而贝塞尔曲线可以模拟几乎任何的曲线,感兴趣的可以深入了解下)

以上,是一个移动动画的例子,当然还有很多种不同的动画形式,但是其循环逻辑方式都是类似的,只是为了实现不同的动画,需要使用不同的算法。

动画流畅度思考

保持高帧率:浏览器的刷新频率一般是 60 帧,当然可以保证60帧的播放动画是最好的,但是有时候因为性能的关系,无法达到,那么就会出现跳帧的现象,观感非常的不好。

保持帧率稳定:也就是保持一定的帧数,可能比一会儿40帧,一会儿60帧带来的效果更好。

现代的js动画库都会做大量的优化工作,为了性能的最大化,比如

  • 缓动效果提前计算
  • GPU加速
    • 有一个css属相叫做will-change,元素添加这个属性的并且指定某一些触发属性(scroll-position, transform, opacity 等),该属性会被提到单独的层通过GPU来预先渲染好,这样可以提高流畅度。当然使用太多也是有副作用的,具体可以看:developer.mozilla.org/en-US/docs/…
  • CPU调度,延迟平缓

为什么需要JS动画

大部分简单动画效果,可以非常方便的使用css来实现,使用JS来制作控制动画的最大的好处就是可以精准的控制动画的过程,可以播放、暂停、重新开始等等,可以实现比较复杂的动画。但是会带来编码的复杂度,当然大多数时候我们不需要手动来造新的轮子,已经有比较好、数量也很多的js动画库存在。下面只是象征性的列举一些:

  • anime.js: 轻量级但是强大的动画库
  • gsap: 商用的,被誉为是性能最好,最强大的JS动画库
  • velocityjs: 知名的,简单易用的js动画库