CRP:关键渲染路径(critical rendering path)
围绕渲染的机制和步骤,去详细的进行每一步优化,以此来提高页面的渲染速度和运行性能
从服务器基于HTTP网络请求回来的数据
- 是一串16进制的文件流
- 浏览器把它解析为字符串(HTML字符串)
- 按照W3C规则识别为成为一个个的HTML节点[词法解析]
- 生成xxx树(CSS叫CSSOM树,HTML叫DOM树)
在浏览器中输入一个网址:
-
首先请求回来的是一个HTML文档,浏览器开始自上而下解析和识别代码(渲染)
-
接下来渲染页面,此过程中
- 遇到style内嵌样式,GUI直接渲染即可[同步渲染]
1.如果CSS代码量比较少,我们直接内嵌即可,拉取HTML的时候,同时CSS也回来了,渲染的时候直接就渲染了
2.如果CSS代码比较多,如果还用内嵌,一方面会影响HTML的拉取速度,也不利于代码的维护,此时还是用外链的方式比较好- 遇到link,浏览器开辟一个HTTP线程去请求资源文件信息,同时GUI继续向下渲染[异步]
1.浏览器同时能够发送的HTTP请求是有数量限制的(谷歌:5~7个)
2.超过最大并发限制的HTTP请求需要排队等待
3.HTTP请求一定是越少越好- 遇到@import,浏览器也是开辟HTTP线程去请求资源,但是此时GUI也暂停了(导入式样式会阻碍GUI的渲染),当资源请求回来之后,GUI才能继续[同步渲染]
真实项目中应该避免使用@import
- 遇到script标签,会阻碍GUI的渲染(内嵌和外链都一样)
- 遇到img/音视频等标签,解析顺序和link差不多
对script标签的优化方式
- window.addEventListener(‘load’,function(){});
- window.addEventListneer(‘DOMContentLoaded’,function(){});
- 在script标签中添加一个async属性/defer属性可以避免阻塞
+ defer请求JS资源是异步的[单独开辟HTTP去请求],此时GUI继续渲染,等到GUI渲染完毕,再去渲染JS[不会阻碍GUI渲染]
+ async请求JS资源是异步的[单独开辟HTTP去请求],此时GUI继续渲染,但是一旦JS请求结束,会立即暂停GUI的处理,接下来去渲染JS
假如我们有5个JS请求,如果不设置任何属性,肯定是按照顺序请求和渲染JS的[依赖关系是有效的]
但是如果设置了async,谁先请求回来就先渲染谁,依赖关系是无效的;
如果使用defer是可以建立依赖关系的(浏览器内部在GUI渲染完成后,等待所有设置defer的资源都请求回来,再按照编写的依赖顺序去加载JS)
在真实项目开发中,我们一般把link放在页面的头部[是为了在没有渲染DOM的时候,就通知HTTP去加载CSS了,这样DOM渲染完,CSS也差不多加载好了,更有效的利用事件,提高页面的渲染速度];
我们一般把JS放在页面的底部,防止其阻碍GUI的渲染,如果不放在底部,我们最好设置上async/defer…
页面渲染过程:
首先生成DOM TREE(触发DOMContentLoaded事件)----- [执行JS] ----- 生成CSSOM TREE ----- 生成RENDER TREE渲染树(浏览器未来是按照这个树来绘制页面的)----- Layout布局计算[回流/重排](计算渲染树中各个节点在设备视口内的确切位置和大小)---- Painting绘制[重绘]{分层绘制}{栅格化布局}…
- 页面第一次渲染,必然会引发一次回流和重绘[很消耗性能:DOM操作消耗性能,90%说的都是它]
- 如果我们改变了元素的位置和大小,浏览器需要重新计算元素在视口中的位置和大小信息,重新计算的过程是回流/重排,一旦发生了回流操作,一定也会触发重绘
- 但是如果只是一些普通样式的改变,位置和大小不变,只需要重绘即可
优化方案:
- 标签语义化和避免深层次嵌套
- CSS选择器渲染是从右到左,所以写选择器的时候不要写太长,太长会导致CSSOM TREE建立过慢
- 尽早尽快地把CSS下载到客户端(充分利用HTTP多请求并发机制)
DOM的重绘和回流 Repaint & Reflow
- 重绘:元素样式的改变(但宽高、大小、位置等不变)
如:outline,visibility,color,background-color等
- 回流:元素的大小或者位置发生了变化(当页面布局和几何信息发生变化的时候),触发了重新布局,导致渲染树重新计算布局和渲染
如:添加或删除可见的DOM元素;元素的位置发生变化;元素的尺寸发生变化;内容发生变化(比如文本变化或图片被另一个不同尺寸的图片所替代);页面一开始渲染的时候(这个无法避免);因为回流是根据视口大小来计算元素的位置和大小的,所以浏览器的窗口尺寸变化也会引发回流
注意:回流一定引发重绘,而重绘不一定引发回流
前端性能优化之:避免DOM的回流
1.放弃传统操作dom的时代,基于vue/react开始数据影响试图模式
- mvvm/mvc/virtual dom/dom diff…
2.样式集中改变
xxx.style.cssTest = “” 给行内批量设置样式
xxx.className = xxx 改变其class名称
新版浏览器有一个机制:渲染队列机制
把修改元素样式的操作,依次存放到渲染队列中
(1) 当修改样式的代码已经没有了
(2) 或者遇到了获取元素样式的代码
(3) style.xxx/getComputedStyle/getBoundingClientRect/clientWidth…/offsetWidth…/scrollWidth…
以上三种方式都会刷新渲染队列:把现有队列中的样式去统一修改和渲染,引发一次回流和重绘
由于这个机制,我们可以进行优化:读写分离
3.读写分离:把获取样式和设置样式的操作分离开
offsetTop/offsetLeft/offsetWidth/offsetHeight/clientTop/clientLeft/clientWidth/clientHeight/scrollTop/scrollLeft/scrollWidth/scrollHeight/getComputedStyle/currentStyle…会刷新渲染队列
4.缓存布局信息
5.元素批量修改 createDocumentFragment()
文档碎片:
let box = document.querySelector('#box');
frag = document.createDocumentFragment();
for(let i = 0; i < 10; i++) {
let span = document.createElement('span');
span.innerHTML = i + 1;
frag.appendChild(span);
}
box.appendChild(frag);
除了这种方式,还可以用模板字符串拼接的形式进行添加新元素
6.动画效果应用到position属性为absolute或fixed的元素上(脱离文档流) CSS是分层渲染的,改变单独层级会比改变整体文档流好
7.CSS3硬件加速(GPU加速)
- 比起考虑如何减少回流重绘,我们更期望的是,根本不要回流重绘;transform/opacity/filters…这些属性会触发硬件加速,不会引发回流和重绘
8.牺牲平滑度换取速度
- 每次1像素移动一个动画,但是如果此动画使用了100%的CPU,动画就会看上去是跳动的,因为浏览器正在与更新回流作斗争,每次移动3像素可能看起来平滑度低了,但它不会导致CPU在较慢的机器中抖动
9.避免table布局和使用css的javascript表达式