前言
当我们在浏览器中输入www.google.com时,他会发生什么?他总共会经历两个过程,一个是浏览器的请求过程,还有一个是浏览器的渲染过程,今天我们简单聊聊浏览器的渲染过程。
浏览器的渲染过程
当我们浏览器拿到请求过来的资源时,首先会将请求数据包的二进制字节数据文件解析为html和css字符串文件,然后浏览器拿到这些字符串数据之后会给这些数据打上token标记即最小代码单位,目的是为了让浏览器更好的分割代码理解语句,打上标记之后紧接着就会生成一些node节点,然后根据这些节点构建dom树,自此浏览器就拿到了一颗dom树。然后浏览器会拿着css字符串文件生成一颗cssom树。现在我们就有两颗树,浏览器会拿着这两棵树去生成一颗rander树,一个小细节,渲染树只会包含显示的节点,也就是说当一段html结构被设置成display为none时,他是不会再渲染树中出现的。然后浏览器就拿着这颗渲染树去计算布局也叫做回流,就是计算元素的位置和大小的过程,然后开始绘制也叫做重绘,即当元素的外观发生变化(如颜色、背景图像、边框样式等)时,浏览器重新绘制元素的过程。自此浏览器的渲染过程到此结束,这是比较简短的描述,里面还包含着非常复杂的过程。
分点方便记忆的话就是这样
解析数据包得到htlm文件,css文件(将二进制的字节数据解析成字符串)
字节数据-->字符串-->token标记-->node节点-->构建dom树
将css文件转化为cssom树
将dom树和cssom树进行合并,得到render树(渲染树只会包含显示的节点)display:none的节点不会被渲染
计算布局(回流)
GPU绘制(重绘)
在我们了解浏览器的渲染过程之后,我们可以通过一些规范技巧来提升我们的代码性能。
优化代码
为什么操作dom慢?
我们在学习vue的时候常说,不要在代码里面操作dom元素,因为操作dom是一件非常消耗性能的行为,但是为什么呢?在浏览器中会有两个线程,一个是js引擎线程,一个是渲染线程,这两个线程他是互斥的,为什么要互斥?我们想象一下,当我们在js中操作样式时,把一个盒子的宽度设置为100px,但是我在css中也设置了50px,当这两个线程同时操作这个盒子的宽度时,那就会乱套,浏览器不知道最终盒子的宽度设置为多少,所以这两个线程一定会互斥,也就是说js引擎线程会阻塞渲染线程的加载。结论,当我们频繁的在代码里面操作dom元素时,就势必会涉及到两个线程的通信和切换,造成性能上的损耗。
所以第一个可以优化的点就是尽量减少操作dom元素。
减少回流重绘次数
回流是一个开销较大的操作,因为浏览器需要重新计算页面布局,可能会导致性能问题,尤其是在包含大量 DOM 元素或频繁触发回流的情况下。因此,在开发网页时,尽量减少不必要的回流操作是很重要的。
虽然重绘的开销较小,但频繁的重绘操作仍然可能影响页面性能。因此,在开发网页时,同样需要注意减少不必要的重绘操作。
什么情况下会触发回流
添加或删除 DOM 元素:在页面中添加或删除元素会导致回流。
修改元素的尺寸、边距、填充或边框:任何这些属性的变化都可能导致回流。
修改元素的内容:例如改变文本内容的大小或添加图片。
调整窗口大小:当用户调整浏览器窗口的大小时,页面布局需要重新计算。
获取布局信息:访问某些属性(如 offsetWidth、offsetHeight、clientWidth、clientHeight 等)时,浏览器可能需要执行回流以确保返回的值是最新的。
CSS 样式的改变:修改影响布局的 CSS 样式属性(如 display、position、width、height 等)也会触发回流。
回流必定会触发重绘,当时重绘不一定会触发回流。
什么情况下会触发重绘
改变元素的颜色:例如改变文本颜色、背景颜色或边框颜色。
改变元素的可见性:例如使用 visibility 属性切换元素的显示状态(与 display 不同,display 会触发回流)。
改变元素的背景图像:例如更新背景图像或背景图像的位置。
改变元素的边框样式:例如修改边框的样式、颜色或宽度。
浏览器中回流重绘的优化
浏览器会维护一个渲染队列,当回流发生时,回流行为会被加入到队列中,在达到阈值或者一定时间之后会一次性渲染队列中的所有回流生效。
当在js代码中写下这些属性时,会强制刷新渲染队列,当渲染队列里面有回流时,就会使回流生效,如果没有就仅仅只是刷新渲染队列,而不会触发回流。
offsetWidth/offsetHeight/offsetTop/offsetLeft
clientWidth/clientHeight/clientTop/clientLeft
scorollWidth/scrollHeight/scrollTop/scrollLeft
这个时候就不得不提到字节的一道面试题了,问触发了几次回流重绘?你知道吗?欢迎评论区留言奥。
let box = document.getElementById('box'); box.style.width = (box.offsetWidth+1)+'px'; box.style.width = 100+'px';
代码的优化
让需要修改几何属性的容器先脱离文档流不显示,修改完几何属性之后再显示。
还记得我们之前说过,当一段html设置display为none时,他是不会再渲染树中出现的。所以也就不会线程互斥了,所以我们可以这么处理。
let ul = document.querySelector('ul'); ul.style.display = 'none'; for(let i=0;i<10000;i++){ let li = document.createElement('li'); li.innerHTML = '我是第'+i+'个li'; ul.appendChild(li); } ul.style.display = 'block';
借助文档碎片Fragment
“文档碎片”(Document Fragment)是一个轻量级的、没有父级的节点,可以用来包含其他节点。它作为一个临时的容器,可以帮助优化 DOM 操作。使用文档碎片可以减少直接对 DOM 的多次操作,从而提高性能。
let frg = document.createDocumentFragment(); let ul = document.querySelector('ul'); for(let i=0;i<10000;i++){ let li = document.createElement('li'); li.innerHTML = '我是第'+i+'个li'; frg.appendChild(li); }