CRP: Critical Rendering Path
关键渲染路径
注意
- 阻塞解析 与 阻塞渲染 是不同的!
关于渲染过程
build DOM -> fetch CSS/fetch JS -> build CSSOM/run JS -> build DOM -> render tree -> layout -> paint
注意:这样的过程并不是严格按照顺序一次性执行完毕的,现代浏览器会边解析边渲染。因此要提高首次渲染速度的关键是,尽早解析更多的DOM,尽早加载解析更多的CSSOM,延迟脚本代码的执行延迟前述两个过程
在非异步渲染(同步渲染)情况下:
- html的加载与解析是阻塞渲染的
- 默认情况下,css的加载与解析是阻塞DOM解析与渲染的(与媒体查询/媒体类型声明有关)(因此需要尽早下载解析以完成首次渲染,所以一般将css样式表放在最前,防止页面发生样式跳动)
- js的加载与解析可能是阻塞渲染(DOM构建)的(与标签声明有关)
- js脚本的执行会阻塞后续的DOM的解析,而前面解析完成的DOM会立即渲染出来,所以一般将js脚本放在最后,以免影响DOM的解析
- 外部样式表/脚本的远程获取是阻塞渲染的
- Font加载阻塞应用到该字体部分的内容渲染,为了避免FOUT(Flash Of Unstyled Text),但是会造成FOIT(Flash Of Invisible Text)问题。只有当字体超过一段时间仍未加载成功时,浏览器才会降级使用系统字体。每个浏览器都规定了自己的超时时间
- 网页其他资源如图像视频等不是阻塞渲染的
- 多个外部的js/css资源可以异步的请求获取,但是解析过程不是并行的
- 外联样式的载入与解析会阻塞JS脚本的执行与DOMContentLoaded事件的触发。 DOM的构建依赖js脚本的执行完成(js脚本的加载执行会阻塞DOM的构建),js的执行依赖脚本所处之前的样式表CSSOM构建完成(CSSOM在js执行前构建完成,只要浏览器遇到 script 标记,就会进行阻止,并等到 CSSOM 构建完毕)。这样的结果是:js可以读取修改DOM/CSSOM的属性,开发者可以在脚本中获取到DOM的样式、位置等信息
- 但是DOM的构建并不会受到CSS的直接影响,只有在存在脚本的情况下,才会先等待CSSOM构建完成,再执行脚本,再继续构建DOM。如果没有脚本,DOM会直接构建完成,触发DOMContentLoaded,再构建CSSOM
在异步渲染的情况下:
- 动态插入的 外联样式或脚本不阻塞 DOM的解析/渲染
- 动态插入的 内联样式或脚本会阻塞 DOM的解析/渲染
- 未连接到DOM的样式表或脚本(创建了link或script标签但尚未append到DOM结构中)不会被下载/解析/执行
- 动态插入的样式或脚本可以监听onload/onerror事件
关于async defer normal \<script> 标签
CRP流程
CRP过程中的关键时间点
- domInteractive:DOM刚刚解析完成构建完成的时间点。
performanceTiming.domInteractive返回这个时间点的时间戳 - DOMContentLoaded:当DOM构建完成,并且其所属script之前的样式表加载解析CSSOM完成时,触发DOMContentLoaded事件
- load: 所有资源加载解析完成时触发
优化CRP
即 最大限度缩短执行渲染过程到paint耗费的总时间
如前述 尽早解析更多的DOM,尽早加载解析更多的CSSOM,延迟脚本代码的执行延迟前述两个过程
“优化关键渲染路径”在很大程度上是指了解和优化 HTML、CSS 和 JavaScript 之间的依赖关系谱。
减少脚本文件数量。可以将多个文件进行打包合并,一方面减少了请求的数量,一方面减少了 等待加载-执行 的重复过程的次数
尽量保持css层级的扁平简单,尽量使用class/id(加快节点查找),以简化CSSOM构建过程,加快构建速度
将脚本置于文件末尾,或者使用defer属性。这样不会因为脚本执行延迟DOM/CSSOM的构建,不会延迟图片等非关键资源加载。chrome会先执行defer再触发DOMContentLoaded事件
使用async属性,这样不会因为远程加载过程阻塞渲染。
尽早加载css文件
尽量不要使用
@import指令导入样式,这样的导入不会并行的加载文件,而是等待样式文件加载完成才会import对样式表使用媒体类型命令/媒体查询条件,只加载关键资源
对样式表文件使用
rel="preload",提前加载资源(但不会立即构建CSSOM),在未来中使用通过js动态加载样式表,这样不会阻塞首次渲染
使用web worker处理耗时js逻辑