大家是不是会遇到这样的一个问题,页面加载速度过慢,浏览器老在转圈圈,页面部分内容需要花费较多的时间才能加载出来?
要明白上述问题,我们需要知道是什么在阻塞页面的渲染?
1、浏览器如何渲染?
1.1、渲染引擎介绍要先说明:Firebox 的渲染引擎是 Geoko,chrome 的渲染引擎是 wekit。本文使用的是 chrome 浏览器
1.2、渲染的主要过程
简单介绍浏览器解析 DOM 生成 DOM Tree, 结合 CSS 生成的 CSS Tree, 最终组成 Render Tree, 再渲染页面。因此 在此过程 css 不会阻塞 DOM 解析。
详细介绍
流程示意图
几个概念:
DOM Tree:浏览器将 HTML 解析成树形的数据结构 CSS Rule Tree:浏览器将 CSS 解析成树形的数据结构 Render Tree:DOM 和 CSSOM 合并后生成 Render Treelayout:有了 Render Tree, 浏览器已经知道网页中有哪些节点、各个节点的 CSS 定义以及他们的从属关系,从而去计算出每个节点在屏幕中的位置 painting:按照算出来的规则,把内容画到屏幕上 reflow(回流):当浏览器发现某个部分发生了点变化影响了布局,需要倒回去重新渲染,这个过程称为 reflow。reflow 会从 <html> 这个 root frame 开始递归往下,依次计算所有的结点尺寸和位置。reflow 是无法避免的。目前界面上流行的一些效果,比如树状目录的折叠、展开(实质上是元素的显示与隐藏)等,都将引起浏览器的 reflow。鼠标滑过、点击等等 … 只要这些行为引起了页面上某些元素的占位面积、定位方式、边距等属性的变化,都会引起它内部、周围甚至整个页面的重新渲染 repaint(重绘):改变某个元素的背景色、文字颜色、边框颜色等等不影响它周围或内部布局的属性时,屏幕的一部分要重画,但是元素的几何尺寸没有变。
注意:(1)display:none 的节点不会被加入 Render Tree, 而 visibility:hidden 则会;所以,如果某个节点最开始是不显示的,设为 display:none 是更优的。(2)display:none 会触发 reflow, 而 visibility:hidden 只会触发 repaint, 因为位置没有变化(3)有些情况下,比如修改了元素的样式浏览器并不会立刻 reflow 或 repaint 一次,而是会把这样的操作积攒一批,然后做一次 reflow,这叫 异步 reflow 或 增量异步 reflow。但是在有些情况下,比如 resize 窗口,改变了页面默认的字体等。对于这些操作,浏览器会马上进行 reflow。
webkit 的主要流程:
Geoko 的主要流程:
完整流程解析:(1)浏览器将 HTML 解析成 DOM 树,当前节点的所有子节点都构建好后才会去构建当前节点的下一个兄弟节点。(2)将 CSS 解析成 CSS Rule Tree(3)根据 DOM 树 和 CSSOM 构造 Render Tree。注意,display:none 不会被挂载到 Render Tree 上面(4)计算出每个节点在屏幕中的位置(5)绘制!注意:渲染引擎将会尽可能早的将内容呈现到屏幕上,并不会等到所有的 html 都解析完成之后再去构建和布局 render tree。它是解析完一部分内容就显示一部分内容,同时,可能还在通过网络下载其余内容
展示一下。display:none 与 visibility:hidden 在浏览器上的区别
display:none
visibility:hidden
2、阻塞渲染:CSS 与 javascript
讨论资源的阻塞时,我们要清楚,现代浏览器总是并行加载资源。例如,当 HTML 解析器(HTML Parser)被脚本阻塞时,解析器虽然会停止构建 DOM,但仍会识别该脚本后面的资源,并进行预加载。
同时,由于下面两点:
默认情况下,CSS 被视为阻塞渲染的资源,这意味着浏览器不会渲染任何已处理的内容,直至 CSSOM 构建完毕
javascript 不仅可以读取和修改 DOM 属性,还可以读取和修改 CSSOM 属性
存在阻塞的 CSS 资源时,浏览器会延迟 javascript 的执行和 Render Tree 构建。
另外
当浏览器遇到一个 script 标记时,DOM 构建将暂停,直至脚本完成执行。
javascript 可以查询和修改 DOM 与 CSSOM
CSSOM 构建时,javascript 执行将暂停,直至 CSSOM 就绪。
所以,script 标签的位置很重要。实际使用时,可以遵循下面 2 个原则
CSS 优先:引入顺序上,CSS 资源优于 javascript 资源
javascript 应尽量少影响 DOM 的构建
2.1、CSS 会阻塞渲染吗?
从浏览器的渲染原理来看,渲染引擎会将 css 构建成 CSSOM Tree 然后再渲染页面。也就是说,CSS 会 阻塞 页面的渲染!但是,CSS 并不会阻塞 DOM 的解析。(因为需要具有 DOM 以及 CSSOM 才会构建渲染树)
2.2、JS 会阻塞渲染吗?
看以下代码:
<!DOCTYPE html>
<html>
<head>
<meta charset=”utf-8″ />
<title></title>
<style>
div {
width: 100px;
height: 100px;
background: lightgreen;
}
</style>
</head>
<body>
<div>dsadddadas</div>
</body>
</html>
下面这段代码会在浏览器上面显示一个绿色的盒子
第 2 个例子,在头部引入 JS
// new_file.js
let arr = []
for (let i = 0; i < 10000000; i++) {
arr.push(i);
arr.splice(i%3, i%7 ,i %5);
}
let div= document.querySelector(‘div’);
console.log(div);
<!DOCTYPE html>
<html>
<head>
<meta charset=”utf-8″ />
<title></title>
<style>
div {
width: 100px;
height: 100px;
background: lightgreen;
}
</style>
<script src=”js/new_file.js”></script>
</head>
<body>
<div>box1</div>
<div style=”visibility: hidden;”>box2</div>
<div>box3</div>
</body>
</html>
浏览器会先转圈圈,最后在显示 盒子
也就是说,为什么大部分程序都会将 js 放在 底部,css 放在顶部 就是为了加速页面的渲染
3、我们如何改变阻塞现状?defer 与 async
3.1、defer 与 async 的介绍
defer:js 的加载不会阻塞页面的渲染和资源的加载,defer 会按照原本 js 的顺序执行。async:js 的加载不会阻塞页面的渲染和资源的加载,一旦加载到就会立刻执行。如果 js 前后有依赖性,最好不要用 async。
3.2、defer 与 async 的区别 3.2.1、相同点:
加载文件时不阻塞页面渲染
<!DOCTYPE html>
<html>
<head>
<meta charset=”utf-8″ />
<title></title>
<style>
div {
width: 100px;
height: 100px;
background: lightgreen;
}
</style>
<script src=”js/new_file.js” defer></script>
</head>
<body>
<div>box1</div>
<div>box3</div>
</body>
</html>
// new_file.js
let arr = []
for (let i = 0; i < 10000000; i++) {
arr.push(i);
arr.splice(i%3, i%7 ,i %5);
}
let div= document.querySelector(‘div’);
console.log(div);
会在一运行的时候,就在页面中显示 2 个盒子, 因此不阻塞
对于 inline 的 script(内联脚本)无效
<script>
let arr = []
for (let i = 0; i < 10000000; i++) {
arr.push(i);
arr.splice(i%3, i%7 ,i %5);
}
console.log(“…”)
</script>
<script async>
console.log(“async”)
</script>
<script defer>
console.log(“defer”)
</script>
浏览器按顺序打印!
使用这两个属性的脚本中不能调用 document.write 方法
// new_file.js
let arr = []
for (let i = 0; i < 10000000; i++) {
arr.push(i);
arr.splice(i%3, i%7 ,i %5);
}
let div= document.querySelector(‘div’);
console.log(div);
document.write(‘asdadasas’);
有脚本的 onload 事件回调
3.2.2、不同点:
每一个 async 属性的脚本都在它下载结束后之后立刻执行,同时会在 window 的 load 事件之前执行。
每一个 defer 属性的脚本都是在页面解析完毕之后,按照原本的属性执行,同时会在 document 的 DOMContentLoader 之前执行
参(抄)考(袭)文章:https://juejin.im/post/59c606… 原来 CSS 与 JS 是这样阻塞 DOM 解析和渲染的
https://juejin.im/entry/59e1d… 浏览器的渲染:过程与原理
https://juejin.im/post/5a1229… script 中 defer 和 async 的区别
https://www.cnblogs.com/Bonni… load/domContentLoaded 事件、异步 / 延迟 Js 与 DOM 解析