误打误撞利用了UI渲染的时机

1,340 阅读4分钟

上次说到 震惊?!无缝轮播图中不让用setInterval控制图片切换?, 然后向项目组中的其他人推广这个操作,当时同事问起原理,我就装了个B,说css动画的优先级位于微任务之后,等css动画执行完毕之后才去执行下一个宏任务(之所以会得出这样的结论是因为我用微任务没效果,用setTimeout是可以的),事后回想起来还是挺心虚的,所以就开始了这篇文章

浏览器中UI渲染与js引擎之间的联系

在此之前我们需要了解任务队列事件循环这两个概念

任务队列

所有的任务可以分为同步任务和异步任务,同步任务,顾名思义,就是立即执行的任务,同步任务一般会直接进入到主线程中执行;而异步任务,就是异步执行的任务,比如ajax网络请求,setTimeout 定时函数等都属于异步任务,异步任务会通过任务队列( Event Queue )的机制来进行协调。

事件循环

同步和异步任务分别进入不同的执行环境,同步的进入主线程,即主执行栈,异步的进入 Event Queue 。主线程内的任务执行完毕为空,会去 Event Queue 读取对应的任务,推入主线程执行。 上述过程的不断重复就是我们说的 事件循环(也叫Event Loop)。 用图表示如下:
在事件循环中,每进行一次循环操作称为tick,每一次 tick 的任务处理模型是比较复杂的,其关键的步骤可以总结如下:

  1. 在此次 tick 中选择最先进入队列的宏任务,如果有则执行
  2. 检查是否存在 微任务 ,如果存在则不停地执行,直至清空 微任务队列
  3. 渲染页面
  4. 主线程重复执行上述步骤

在了解这些之后就可以看出,我之前装逼所说的内容完全是瞎说的,正确来说应该是 因为UI渲染(也叫GUI线程)与js引擎(也叫js线程)互斥,只有当事件循环中的一个宏任务执行完毕后,js引擎挂起,UI渲染才能进入循环,当渲染完毕后,事件循环开始引入并执行下一个宏任务

下面举几个简单的例子更方便去理解
首先写个颜色变化的例子,让页面闪动一下红色

document.body.style.background = 'red';
document.body.style.background = 'white';

上述代码一定达不到效果,背景会稳定地呈现白色。 因为上述两行代码都是属于同步任务,在两者之间不可能插入渲染任务。 这时可能有人想到 setTimeout:

document.body.style.background = 'red';
setTimeout(function () {
    document.body.style.background = 'white';
},0)

大家把这段代码放到f12中执行,明显是可以看到闪动的。但是这样两次背景设置会在不同的任务中执行,如果这两个任务之间插入了渲染任务背景就会发生闪动, 而渲染任务是 1000/60ms 一次(假定你的显示器的刷新频率是60Hz),你怎么知道浏览器会正好插入在这两个任务之间?
因此上述代码只会几率性起作用。
所幸浏览器给了专有的API--window.requestAnimationFrame,这样就可以让浏览器在下一次重绘之前执行你的操作

requestAnimationFrame(function () {
    document.body.style.background = 'red';
    requestAnimationFrame(function () {
        document.body.style.background = 'white';
    })
})

实际工作开发中的例子

比如我之前所写的轮播图,核心代码就是下面这几行

requestAnimationFrame(function () {
    lists.classList.remove("is-animating")
    lists.style.transform = `translateX(${0}px)`;
    requestAnimationFrame(function(){
    	lists.classList.add("is-animating")
        const left = -current * width
        lists.style.transform = `translateX(${left}px)`;
    })
})

翻译成文字就是当图走到最后一个的时候,去掉动画效果,然后移动,移动完之后在加上动画效果,然后整个轮播图就会很连贯

参考文章如下:
事件循环是如何影响页面渲染的?
深入理解JavaScript事件循环机制
Javascript事件循环机制以及渲染引擎何时渲染UI