导语: 适当的使用动画可以是用户体验得到较高的提升

首先得理解下,什么是动画?

下面是摘自维基百科对于动画的定义:

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

非常易于理解的定义,我们看的视频、电影等影视作品,都是某种动画,一般像电影是1秒24张画面。

什么是JS动画

从某种角度,浏览器就是一个动画播放器、一个画布,通常以每秒60帧,也就是每秒60张画面,当然这个和视频、电影还是不同,主要在于浏览器是动态的计算每一帧的画面,然后展示的。

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

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

如何创作JS动画

基本循环

在浏览器中使用 requestAnimationFrame 方法可以创建一个动画循环

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

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

  1. function step(timestamp) {
  2. /**
  3. * 这里可以添加一些改变元素的位置、颜色等属性,从而触发浏览器重新渲染的逻辑
  4. */
  5. // 继续循环,下一帧
  6. window.requestAnimationFrame(step);
  7. }
  8. window.requestAnimationFrame(step)

这是一个无线循环,不可能任何动画都是不断播放的没有止境的,一般动画都是有开始和结束时间点,也就是动画有一个持续的时间。这就需要我们能够比较好的控制每一帧的动画,从而达到各种效果。

动画控制

既然浏览器负责绘制渲染,所以JS的本质就是控制属性值根据某种规律的每一帧的变化。

比如把一个元素2s内线性右移 200px的距离,那么可以很容易计算出当前右移的距离. (200 / 2000 ) * 已持续的时间。下面是简单的代码事例:

  1. let start = null
  2. function step(timestamp) {
  3. if (!start) start = timestamp
  4. const element = document.getElementId('some-element');
  5. // 动画已经执行的时间
  6. const progress = timestamp - start;
  7. // 播放到当前时间的值。 这里是一个线性的过程
  8. const progressValue = (200 / 2000) * progress
  9. // 向右移动到当前播放的位置
  10. element.style.transform = `translateX(${Math.min(progressValue, 200)}px)`;
  11. // 继续循环,下一帧。超过2s结束动画播放
  12. if (progress < 2000) window.requestAnimationFrame(step);
  13. }
  14. window.requestAnimationFrame(step)

线性移动.gif

缓动效果

缓动效果可以通过一个缓动函数(easing function)来实现。

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

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

  1. // 播放到当前时间的值。 这里是一个线性的过程
  2. const progressValue = (200 / 2000) * progress
  3. // 替换为下面。先慢后快的缓动效果
  4. const elapsed = progress / duration
  5. const easingCirc = 1 - Math.sqrt(1 - elapsed * elapsed)
  6. const progressValue = (200 / 2000) * progress * easingCirc

easingInCirc缓动右移.gif

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

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

  • 正弦:

    1. t => 1 - Math.cos((t * Math.PI) / 2)
  • 圆形:

    1. t => 1 - Math.sqrt(1 - t * t)
  • 后退:

    1. t => t * t * (3 * t - 2)

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

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

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

动画流畅度思考

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

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

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

  • 缓动效果提前计算
  • GPU加速
  • CPU调度,延迟平缓

小小的加速技巧

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

为什么需要JS动画

需求简单的动画,可以非常方便的使用css来实现。

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

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