浏览器渲染的本质
浏览器渲染是指浏览器将接收到的HTML、CSS和JavaScript等网页资源转换成用户可以在屏幕上看到的可视化内容的过程。这个过程包括解析文档、构建渲染树、布局计算以及最终的绘制四个主要阶段。
1. 解析文档
当浏览器接收到服务器返回的HTML文档后,它会开始解析这些文档。浏览器按照文档流的顺序逐行读取HTML代码,并构建出DOM(文档对象模型)。DOM是一个树状结构,代表了文档中的所有元素及其关系。
2. 构建渲染树
在DOM构建完成之后,浏览器会继续解析CSS样式表,并将它们应用到相应的DOM节点上。然后,浏览器会根据DOM树和CSSOM(CSS对象模型)构建渲染树。渲染树包含了所有需要显示的元素及其样式信息。
3. 布局计算
有了渲染树后,浏览器接下来会对树中的每一个节点进行布局计算,确定每个元素的位置、大小和其他几何属性。在这个阶段,浏览器会解决元素之间的相对位置关系,例如浮动、定位等。
4. 绘制
一旦布局计算完成,浏览器就可以将渲染树中的各个元素绘制到屏幕上。这个过程涉及到将元素的视觉属性(如颜色、字体、背景等)应用到屏幕上对应的像素点上。
5. 重排与重绘
如果网页内容发生变化(比如添加或删除DOM节点),浏览器需要重新进行布局计算,这就是所谓的“重排”。而如果只是样式变化而不影响布局,则只需要“重绘”。
理解浏览器渲染过程对于优化网站性能至关重要,因为合理的代码编写和资源加载策略能够减少不必要的重排和重绘,提高用户体验。 好了,这就是浏览器渲染的本质的大概介绍!!接下来就进入正题吧!!
形象了解浏览器渲染
我们试想一下当我们在浏览器上输入www.baidu.com 会发生什么呢?当我们按下回车键的时候又会发生什么呢?www.baidu.com 这是一段域名,其实说白了就是给IP地址套一层壳,通过域名可以解析为IP地址。说白了正是有域名这个概念才让我们访问网页更加方便。我们不用输入像192.168.1.1这种的一串数字。 当我们输入我们在浏览器上输入www.baidu.com 后电脑根本无法识别这个东西。电脑拿到这个域名后首先会进行DNS解析,可以帮我们把这该域名分析出来究竟是那个IP地址,网络通讯就是要确定双方的IP。然后解析出来的这个IP地址匹配百度的IP地址。第二步,通过IP协议确定双方的位置连的是哪里的网。第三步呢就是开始进行通讯。通讯之前先通过TCP协议进行建立连接,然后就开始数据传输。(走的是http协议)然后我就可以向百度服务器发送请求,请求就是一个个数据包(js写的ajax请求)。然后百度服务器再返回一个个数据包。然后进行TCP的四次挥手断开连接,此时就是一次计算机和百度服务器进行的一次通信,一个请求出去,一个响应回来了。这就是浏览器的请求过程大概!! 好了,那我们进入正题,谈谈拿回数据包后电脑该干什么事情。 首先呢浏览器会解析数据包得到html文件,css文件(将二进制的字节数据解析为字符串)
之后呢 浏览器将根据转化好的字符串进行词法分析转换做标记。流程如: 字节数据==》字符串==》(标记)Token==》Node节点(对象来描述)==》构建DOM树。最后将css文件转化为CSSOM树。浏览器会找到所有的span然后筛选出来哪些span是在a标签里边,然后又去找哪些a标签是被div包裹。 因此最好是不要用具体的标签选择器写css样式这样会消耗很多的性能,最好是取类名。
div>a>span{
color:red
}
接下来DOM+CSSOM==render树(渲染树只会包含显示的节点) 最后计算页面布局(回流),GPU绘制(重绘)
操作DOM慢的原因
当我们用js来操作DOM的时候就会有两个线程一个是页面的渲染线程,一个是js引擎线程。js这门编程语言可以操作html,如果同时工作两个线程可能会撞车,这就是一种不安全的渲染。因此是互斥的,因此js引擎线程的执行一定会阻塞html的加载,当我们通过js来操作DOM的时候就会涉及到两个线程的通信和切换,会造成性能上的损耗。我们可以把js写在html的下面。不仅仅是因为我们要获取到html里边的结构,写到上边获取不到。其次呢出于用户体验考虑,如果先执行js代码的话可能会消耗一段时间,那么用户打开页面后就会出现一段时间的空白,等js执行完后页面才开始渲染这样的效果很不好。
回流与重绘
回流: 1.页面初次渲染 2.增加、删除可见的DOM元素 3.改变元素的几何信息 4.窗口大小改变 5.字体大小的更改
重绘: 1.非几何信息被修改
它俩关系可以总结为回流包含于重绘,回流必定导致重绘,重绘不一定导致回流。回流开销性能是很大的。
let el=document.getElementById('app')
el.style.width=(el.offsetWidth+1)+'px'
el.style.width=1+'px'
这段代码出现了一次回流,如果没有渲染队列则是有两次。
浏览器的优化
浏览器会维护一个渲染队列,当改变元素的几何属性导致回流发生时候,回流行为 会被加入到渲染队列中,在达到阈值或者一定时间后(代码执行完毕)会一次性将渲染队列中所有的回流生效。
div.style.left='10px'
div.style.top='10px'
div.style.width='10px'
div.style.height='10px'
只回流一次
div.style.left='10px'
console.log(div.offsetLeft);
div.style.top='10px'
console.log(div.offsetTop);
div.style.width='10px'
console.log(div.offsetWidth);
div.style.height='10px'
console.log(div.offsetHeight);
读取这种几何信息后渲染队列强制刷新,回流四次。 offsetTop,offsetLeft, offsetWidth,offsetHeight, clientTop,clientLeft, clientWidth,clientHeight, scrollTop,scrollLeft, scrollWidth,scrollHeight,
减少回流
1.让需要修改该几何属性的容器先脱离文档流不显示, 修改完后再回到文档流当中。这样可以大大提升性能 例如:
let ul=document.getElementById("demo");
ul.style.display="none";
for(let i=0;i<10000;i++){
let li=document.createElement("li");//创建一个新的li列表元素
let text=document.createTextNode(i);//创建文本节点
li.appendChild(text)
ul.appendChild(li);
}
ul.style.display="block";
2.借助文档碎片
let ul=document.getElementById("demo");
let frg=document.createDocumentFragment()//文档碎片
for(let i=0;i<10000;i++){
let li=document.createElement("li");//创建一个新的li列表元素
let text=document.createTextNode(i);//创建文本节点
li.appendChild(text)
frg.appendChild(li)
}
ul.appendChild(frg)
3.克隆元素
let ul=document.getElementById("demo");
let clone=ul.cloneNode(true)
for(let i=0;i<10000;i++){
let li=document.createElement("li");//创建一个新的li列表元素
let text=document.createTextNode(i);//创建文本节点
li.appendChild(text)
clone.appendChild(li)
}
ul.parentNode.replaceChild(clone,ul);