前言

上周在使用 element-ui 进行项目开发时,为了能够更好的提升用户的使用体验,便在页面设置了 v-loading,用于完成发送请求与页面回显间的过渡,但却因此发生了意想不到的问题。

  • 理想情况:进入该页面或在该页面完成刷新后,将 v-loading 置为 true,随后发送请求,请求过程中持续展示 v-loading 动画,直至使用请求返回的数据完成整个页面中 jsMind 思维导图的渲染。

  • 问题描述:v-loading 动画会首先正常播放,随后停止,并保持停止时的状态直至整个页面中的 jsMind 思维导图完成渲染,随后消失。

  • 问题分析:通过分析代码及现象,初步确定问题产生的原因为 jsMind 中的 JS 代码阻塞了页面中的 CSS 动画,从而导致 v-loading 出现了卡顿现象。

于是针对以上信息,我进行了资料的查阅,下面就部分结论与大家进行探讨。

脱离 UI 线程的 CSS 动画

什么是脱离 UI 线程的 CSS 动画?

由于浏览器是单线程的。在ECMAScript上执行一些复杂的操作会导致页面的阻塞。为了尽量避免这种情况的发生,2013年时,谷歌提议将部分 CSS 动画在 UI 线程中移除。随着时间的推移,一些浏览器(safari和chrome等)采纳并实现了该提议。因此 css transform 之类的 css 动画不会被 js 阻塞了,而使用 property 的动画还是会被阻塞。

使用条件

  • 浏览器支持: iOS Safari, Chrome, Edge
  • 需要使用 CSS transform. 设置常规 animation 属性的动画不起作用。

测试Demo

这是一个带有一些动画的测试页面,点击kill按钮,看看会发生什么。

  • 旋转的红色框的动画如下:
.spin {
  animation: 3s rotate linear infinite;
}
 
@keyframes rotate {
  from {transform: rotate(0deg);}
  to {transform: rotate(360deg);}
}
  • 绿色框也使用 transform 设置动画:
.walkabout-new-school {
  animation: 3s slide-transform linear infinite;
}
 
@keyframes slide-transform {
  from {transform: translatex(0);}
  50% {transform: translatex(300px);}
  to {transform: translatex(0);}
}
  • 蓝色框使用margin-left属性(而不是transform)制作动画:
.walkabout-old-school {
  animation: 3s slide-margin linear infinite;
}
 
@keyframes slide-margin {
  from {margin-left: 0;}
  50% {margin-left: 100%;}
  to {margin-left: 0;}
}
  • kill按钮将 CPU 固定在一个循环中2秒钟:
function kill() {
  var start = +new Date;
  while (+new Date - start < 2000){
     console.log('kill');
  }
}

通过点击可以发现,在大多数不支持的浏览器中(如 FireFox),kill按钮会使所有动画被阻塞。 在支持的浏览器(iOS Safari, Chrome 和 Edge)中,kill按钮只影响蓝色框,即对 CSS 设置 animation 属性的元素,而对使用 CSS transform 的元素不起作用。我们还可以发现,在 animation 动画在被阻塞的时间里,其实动画是仍然在进行的,只是无法在页面正常显示了。

kill 函数会阻塞主进程,阻止页面 render,但是整个 transform 动画被浏览器丢给 GPU 执行(GPU 加速),不会被阻塞。

image.png

animation 动画受主进程控制。主进程被阻塞了,动画就会停下来。

image.png

拓展

tansform, transition 和 animation 的区别

  • transform本身是没有过渡效果的,它只是对元素做大小,旋转,倾斜等各种变换,通过和transition或者animation相结合,可以让这一变换过程具有动画的效果,它通常只有一个到达态,中间态的过渡可以通过和transition或者animation相结合实现,也可以通过js设置定时器来实现.

  • transition一般是定义单个或多个css属性发生变化时的过渡动画,比如width,opacity等.当定义的css属性发生变化的时候才会执行过渡动画,animation动画一旦定义,就会在页面加载完成后自动执行.

  • transition定义的动画触发一次执行一次,想要再次执行时需要再次触发.animation定义的动画可以指定播放次数或者无限循环播放. transition: 需要用户操作,执行次数固定.

  • transition定义的动画只有两个状态,开始态和结束态,animation可以定义多个动画中间态,且可以控制多个复杂动画的有序执行.

总结

由此可见,v-loading 动画并不在脱离 UI 线程的 CSS 动画中,所以不管在哪个浏览器中,都会被较为复杂的 js 代码所阻塞。在本文遇到的问题场景中,jsMind 在获取到所需数据后,会进行部分较为复杂的 js 逻辑,因此阻塞了加载动画,导致了 loading 动画的停止问题。

本文借鉴了 文章解析transform,transition和animation的区别知乎博主 问题回答中的部分内容。