从上面这个图上,咱们能够看到,浏览器渲染过程以下javascript
HTML
生成DOM
树,解析CSS
生成CSSOM
树DOM
树和CSSOM
树结合生成渲染树renderTree
Layout
(回流): 根据生成的渲染树,进行回流(Layout
),获得节点的几何信息(位置,大小)Painting
(重绘): 根据渲染树以及回流获得的几何信息,获得节点的绝对像素 Display
:将像素发送给GPU,展现在页面上。RenderTree
)为了构建渲染树,浏览器主要完成了如下工做css
DOM
树的根节点开始遍历每一个可见节点。CSSOM
树中对应的规则,并应用它们。第一步中,既然说到了要遍历可见的节点,那么咱们得先知道,什么节点是不可见的。不可见的节点包括:html
script、meta、link
等。css
进行隐藏的节点。好比display:none
。注意,利用visibility
和opacity
隐藏的节点,仍是会显示在渲染树上的。只有display:none
的节点才不会显示在渲染树上。Layout
)前面咱们经过构造渲染树,咱们将可见DOM
节点以及它对应的样式结合起来,但是咱们还须要计算它们在设备视口(viewport
)内的确切位置和大小,这个计算的阶段就是回流。
为了弄清每一个对象在网站上的确切大小和位置,浏览器从渲染树的根节点开始遍历,而在回流这个阶段,咱们就须要根据视口具体的宽度,将其转为实际的像素值java
Painting
)经过回流(Layout
)阶段,咱们知道了全部的可见节点的样式和具体的几何信息(位置、大小),那么咱们就能够将渲染树的每一个节点都转换为屏幕上的实际像素,这个阶段就叫作重绘节点。canvas
回流阶段是计算节点的几何信息和位置,那么当页面布局或者几何信息发生改变时,就须要回流。浏览器
DOM
元素注意:回流必定会触发重绘,而重绘(非几何信息的样式发生改变)不必定会回流, reflow回流的成本开销要高于repaint重绘,一个节点的回流每每回致使子节点以及同级节点的回流;缓存
根据改变的范围和程度,渲染树中或大或小的部分须要从新计算,有些改变会触发整个页面的重排,好比,滚动条出现的时候或者修改了根节点。app
Layout
)、重绘(Painting
)的优化方法在现代浏览器的中,因为每次回流、重绘的时候,都须要额外的计算消耗,所以会经过队列化修改,并批量执行来优化这一过程。浏览器会将修改操做放入队列里面,直到过了一段时间或者达到一个阈值,才清空队列。async
可是当你获取布局信息时,会强制刷新队列,例如:ide
offsetTop、offsetLeft、offsetWidth、offsetHeight scrollTop、scrollLeft、scrollWidth、scrollHeight clientTop、clientLeft、clientWidth、clientHeight getComputedStyle() getBoundingClientRect()
上面这些方法,都须要获取最新的布局信息,因此浏览器会强制刷新队列并执行回流、重绘,来获取最新的信息。
所以咱们在修改样式的时候,应该尽可能避免使用上面的属性、方法,若是非要使用,能够先缓存起来而后一块儿获取。
CSS
的修改方式考虑如下代码
const el = document.getElementById('el') el.style.padding = 'xxx' el.style.margin = 'xxx' el.style.border = 'xxx'
这里元素的几何信息有三次被修改了,可是现代浏览器会将起缓存起来,可是若是这期间有经过前面列出来的属性、方法访问位置信息的话就会触发三次回流、重绘。因此仍是建议经过cssText
或者class
的方法一次性修改。
el.style.cssText += 'border-left: 1px; border-right: 2px; padding: 5px;'; // 或者 el.className += 'xxx';
DOM
当咱们须要对DOM
进行一系列修改的时候,能够经过如下几种方式减小回流重绘次数:
function appendDataToElement (appendToElement, data) { let li; for ( let i = 0; i < data.length; i++) { li = document.createElement('li'); li.textContent = 'text'; appendToElement.appendChild(li); } } const ul = document.getElementById('list'); ul.style.display = 'none'; // 首先脱离文档流 appendDataToElement(ul, data); ul.style.display = 'block'; // 操做完之后再可见
document fragment
)在当前DOM
以外构建一个子树,再把它拷贝回文档。const ul = document.getElementById('list'); const fragment = document.createDocumentFragment() appendDataToElement(fragment , data); ul.appendChild(fragment )
一个图层的回流和重绘只会在该图层当中进行,不会影响其余图层,因此有必要的时候,能够将某些元素放到单独的图层。
例如对于复杂动画效果,因为会常常的引发回流重绘,所以,咱们可使用绝对定位,让它脱离文档流, 成为一个单独的图层。不然会引发父元素以及后续元素频繁的回流。可是因该尽可能少许使用图层,由于图层的合成是特别消耗性能,一个页面当中不能有过多的图层, 在使用了图层以后须要进行先后对比
会自动创建图层的状况:
CSS
与 JS
是这样阻塞 DOM
解析和渲染的经过<script>
与<link>
引入外部资源,当解析到该标签的时候,会进行下载。
CSS
脚本的加载不会阻塞 DOM
解析过程,可是会阻塞渲染过程(painting
)JS
脚本的加载与执行会阻塞 DOM
解析过程, 可是不会阻塞后续资源的加载JS
脚本的加载中,若是你肯定不必阻塞 DOM
解析的话,不妨按须要加上 defer
或者 async
属性,此时脚本下载的过程当中是不会阻塞 DOM
解析的。<script>
且没有 defer
或 async
属性的标签时,为了为<script>
标签内部的js
提供最新的信息,会触发页面的回流、重绘过程。CSS
资源还没有加载完毕时,浏览器会等待它加载完毕以后再执行脚本。即 css
不阻塞 js
的加载,但阻塞它的执行。因此<script>
最好放底部(防止阻塞DOM
解析)。<link>
最好放头部(为渲染过程提供样式)。若是头部同时有<script>
与<link>
的状况下,最好将<script>
放在<link>
上面(为了防止CSS
脚本加载时间过长,使js
等待时间也很长)dd
defer
和async
直接看图吧,绿色的表明 html
解析,蓝色的表明 javascript
脚本的下载,红色的表明 javaScript
脚本的执行。