Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

渲染和异步(requestAnimatinFrame和setTimeout) #52

Open
huangchucai opened this issue Oct 21, 2018 · 0 comments
Open

渲染和异步(requestAnimatinFrame和setTimeout) #52

huangchucai opened this issue Oct 21, 2018 · 0 comments

Comments

@huangchucai
Copy link
Owner

huangchucai commented Oct 21, 2018

浏览器渲染

我们知道从用户输入地址到用户看到内容,是有一个过程的,一道经典的面试题。

问:在浏览器输入网址到看到页面经历了哪些过程

  1. 浏览器根据对应的域名发送DNS请求,获取到对应的IP
  2. 获取对应的IP后,根据IP协议传输数据,发送给互联网
  3. 在互联网中路由器根据目标的IP地址,通过复杂的算法找出‘最佳线路’来传输请求
  4. 找到服务器的网卡通过TCP三次握手建立连接,在第三次握手的时候发送HTTP请求
  5. 服务器对请求进行分析,然后返回对应的服务器资源
  6. 浏览器拿到数据后,然后通过TCP四次挥手关闭连接,然后进行渲染
  7. 对HTML进行渲染,解析出DOM树,对css解析出styls Rules,然后关联二者生成Render Tree
  8. Layout根据Render Tree进行计算每个节点的信息
  9. Painting根据计算好的信息绘制页面

渲染解析

从上面的问题我们可以看出,渲染主要是分为三部分

  1. 构建DOM树 - (Structure)
  2. 根据规则确认每一个DOM的位置(render DOM) - Layout
  3. 绘制每一个DOM的位置

注意:但是浏览器不会一直的渲染,因为渲染的开销还是很大的,渲染引擎和js解析引擎互斥的,所以浏览器会合并优化。

浏览器的合并优化

一段同步的代码修改一个元素的属性,浏览器会直接优化到最后一个

box.style.display = "none";
box.style.display = "block";
box.style.display = "none";

所有浏览器并不是一直在渲染的,浏览器会有固定的节奏去渲染页面。

这就会出现一些问题,例如下面的例子

// 我们计划物体先从0px移动到1000px,然后动画回到500px的位置
box.style.transform = 'translateX(1000px)'
box.style.transition = 'transition 1s ease'
box.style.transform = 'translateX(500px)'

但是结果却不是,浏览器会直接的渲染最后一句,把物体移动500px。这里我们就有一个疑问,那么浏览器的渲染到底什么时候执行呢?

主线程和异步队列

我们都知道node.js的event loop,但是浏览器内部也有一个event loop的模式,当主线程空的时候,才会去tasks中推callback到主线程执行。

default

当主线程为空的时候,异步队列开关开。当我们执行下面的一段代码的时候, 进入死循环,点击后会导致异步队列永远执行,因此不单单主进程,渲染过程也同样被阻塞而无法执行,因此页面无法再选中(因为选中时页面表现有所变化,文字有背景色,鼠标也变成 text),也无法再更换内容。(但鼠标却可以动!)

button.addEventListener('click', () => {
    while(true);
})

default

requestAnimationFrame

是一个特殊的异步任务,只是它注册的方法(callback)不会加入异步队列,而是加入渲染这一边的队列中,它在渲染的三个步骤之前被执行。通常用来处理渲染相关的工作

raf

有时候我们不是很懂setTimeout和requestAnimationFrame的区别,今天来一个例子来说明他们的区别:

// moveBoxForwardOnePixel: 让元素像右移动一像素
function moveBoxForwardOnePixel(className) {
    const node = document.querySelector(`.${className}`)
    const rect = node.getBoundingClientRect();
    node.style.left = `${rect.left + 1}px`;
}

function asf() {
    moveBoxForwardOnePixel('rect1');
    requestAnimationFrame(asf)
}

 function set() {
    moveBoxForwardOnePixel('rect2')
    setTimeout(set, 0);
 }
asf()
set()

**重点:**通过上面的例子,我们可有看到,同样的方法,setTimeout运行的次数比asf运行的多很多。(模拟过,一段时间内1000/60也比asf执行多了很多)代码地址

  1. 这是因为setTimeout在每次运行结束时都把自己的callback放入到异步队列中,当主线程空闲的时候,异步队列中callback会被执行,等渲染过程的时候(不是每次的异步队列都会进到渲染循环,就是说并不会执行异步队列就走进渲染开关)异步队列已经执行好多次了(setTimeout的callback已经执行了很多次),所以渲染部分会一次性渲染很多像素,而不是1px。
  2. 但是requestAnimationFrame只会在渲染过程之前运行,因此严格的按照执行一次渲染一次,所以一次只移动1px,也正是我们想要的效果。
  3. 如果在低端环境兼容,常规也会写作 setTimeout(callback, 1000 / 60) 来大致模拟 60 fps 的情况,但本质上 setTimeout 并不适合用来处理渲染相关的工作。因此和渲染动画相关的,多用 requestAnimationFrame,不会有掉帧的问题(即某一帧没有渲染,下一帧把两次的结果一起渲染了)

其他运用

asf主要是用于渲染,但是其他方面也有使用,例如:我们可以对上面的例子进行实现。

// 我们计划物体先从0px移动到1000px,然后动画回到500px的位置
box.style.transform = 'translateX(1000px)'
box.style.transition = 'transition 1s ease'
box.style.transform = 'translateX(500px)
//上面的这种方式会被浏览器的合并优化掉,所以我们可以使用asf
box.style.transform = 'translateX(1000px)'
requestAnimationFrame(() => {
    box.style.transition = 'transition 1s ease'
    box.style.transform = 'translateX(500px)'
})
// 当然我们也可以利用重绘和重排来阻断浏览器的合并优化
box.style.transform = "translateX(100px)";
console.log(box.offsetWidth);
box.style.transition = "transform 1s ease";
box.style.transform = "translateX(200px)";

具体例子可以查看asf的运用

参考链接

  1. 深入浏览器的事件循环
  2. 前端必须懂的计算机网络知识—(跨域、代理、本地存储)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant