Browser page rendering principle, reflow, repaint and optimization

The main components of browsers include:

1. The user interface - including the address bar, forward / back buttons, bookmarks menus. In addition to the page you requested displayed in the main browser window, various other parts of the show are part of the user interface.

2. The browser engine - transfer instructions between the user interface and rendering engine.

3. rendering engine - responsible for displaying the contents of the request. If the requested content is HTML, it is responsible for parsing HTML and CSS content, and the parsed content displayed on the screen.

4. Network - for network calls, such as HTTP requests. Its interface platform-independent, and provide the underlying implementation for all platforms.

The rear end of the user interface - for drawing basic widgets, such as combo boxes and windows. Which discloses a universal interface platform-independent, using the underlying operating system's user interface method.

6. JavaScript interpreter. JavaScript code for parsing and execution, such as chrome JavaScript interpreter is V8.

7. The data storage. This is the persistence layer. The browser needs to save all kinds of data on the hard disk, such as Cookie. The new HTML specification (HTML5) defines 'web database', which is a complete (but lightweight) in the browser database.

 

Critical Path Rendering (Critical Rendering Path): Progressive

 

After the browser rendering process to get HTML :( different kernel implementation is not the same but something like this)

1. parsing HTML, build DOM tree.

2. Parse the CSS, to build CSSOM tree.

3. Combined DOM tree and CSSOM tree, generated render tree.

4. The layout (layout / reflow), calculated for each element size, position.

5. Draw (paint / repaint), page rendering pixel information.

6. The browser sends the information to the layers GPU, GPU synthesis of the layers displayed on the screen.

When changing the DOM or CSSOM, some of the steps in the above process will be repeatedly executed.

 

Construction of OM: to go through Bytes  characters  tokens  nodes  object modelthis process.

 

TIPS:

解析HTML遇到外部CSS立即请求 ----CSS文件合并,减少HTTP请求;

新的CSS style修改CSSOM,会重新渲染页面 ----CSS文件应放在头部,缩短首次渲染时间

遇到<img>会发出请求,但不会阻塞,服务器返回图片文件,由于图片占用了一定面积,影响了后面段落的排布,因此浏览器需要回过头来重新渲染这部分代码;(最好图片都设置尺寸,避免重新渲染)

遇到<script> 标签,会立即执行js代码,阻塞渲染。(script最好放置页面最下面)

js修改DOM会重新渲染。 (页面初始化样式不要使用js控制) 

 

reflow回流:

当某个部分发生了变化影响了布局,需要倒回去重新渲染, 该过程称为reflow(回流)。reflow 几乎是无法避免的。现在界面上流行的一些效果,比如树状目录的折叠、展开(实质上是元素的显 示与隐藏)等,都将引起浏览器的 reflow。鼠标滑过、点击……只要这些行为引起了页面上某些元素的占位面积、定位方式、边距等属性的变化,都会引起它内部、周围甚至整个页面的重新渲染。通常我们无法预估浏览器到底会 reflow 哪一部分的代码,它们彼此相互影响。

repaint重绘:

如果只是改变某个元素的背景色、文字颜色、边框颜色等等不影响它周围或内部布局的属性,将只会引起浏览器 repaint(重绘)。repaint 的速度明显快于 reflow(在IE下需要换一下说法,reflow 要比 repaint 更缓慢)。

reflow一定引起repaint,而repaint不一定要reflow。reflow的成本比repaint高很多,DOM tree里每个结点的reflow很可能触发其子结点、祖先结点、兄弟结点的reflow。reflow(回流)是导致DOM脚本执行低效的关键因素之一。

 现代浏览器会对回流做优化,它会等到足够数量的变化发生,再做一次批处理回流。

 GoogleChromeLabs里面有一个csstriggers,列出了各个CSS属性对浏览器执行Layout、Paint、Composite的影响。

 

在哪些情况下会导致reflow发生:

l  改变窗囗大小

l  改变文字大小

l  添加/删除样式表

l  内容的改变,如用户在输入框中敲字

l  激活伪类,如:hover (IE里是一个兄弟结点的伪类被激活)

l  操作class属性

l  脚本操作DOM

l  计算offsetWidth和offsetHeight

l  设置style属性

 

优化,尽量避免reflow:

l  尽可能限制reflow的影响范围,修改DOM层级较低的结点。不要通过父级元素影响子元素样式。最好直接加在子元素上。改变子元素样式尽可能不要影响父元素和兄弟元素的尺寸。

l  不要一条一条的修改DOM的style,最好通过设置class的方式。 避免触发多次reflow和repaint。

l  经常reflow的元素,比如动画,position设为fixed或absolute,使其脱离文档流,不影响其它元素的布局。

l  权衡速度的平滑。比如实现一个动画,以1个像素为单位移动这样最平滑,但reflow就会过于频繁,CPU很快就会被完全占用。如果以3个像素为单位移动就会好很多。

l  不要用tables布局。tables中某个元素一旦触发reflow就会导致table里所有的其它元素reflow。在适合用table的场合,可以设置table-layout为auto或fixed,这样可以让table一行一行的渲染,这种做法也是为了限制reflow的影响范围。

l  避免使用css expression(每次都会重新计算)。

l  减少不必要的 DOM 层级(DOM depth)。改变 DOM 树中的一级会导致所有层级的改变,上至根部,下至被改变节点的子节点。这导致大量时间耗费在执行 reflow 上面。

l  避免不必要的复杂的 CSS 选择器,尤其是后代选择器(descendant selectors),因为为了匹配选择器将耗费更多的 CPU。

l  尽量不要频繁的增加、修改、删除元素,可以先把DOM节点抽离到内存中进行复杂的操作然后再display到页面上。(display:none的节点不会被加入render tree,而visibility:hidden会;display:none会触发reflow,而visibility:hidden只会触发repaint,因为layout没有变化)。

 

让要进行复杂操作的元素进行“离线处理”,处理完后一起更新:

1.          使用DocumentFragment, DocumentFragment节点不属于文档树,继承的parentNode属性总是null。

 

 
//不建议的做法
 
for(var i = 0 ; i < 10000; i ++) {
 
var p = document.createElement("p");
 
var oTxt = document.createTextNode("段落" + i);
 
p.appendChild(oTxt);
 
document.body.appendChild(p);
 
}
 
//将这些元素添加到DocumentFragment中,再将DocumentFragment添加到页面
 
var oFragment = document.createDocumentFragment();
 
for(var i = 0 ; i < 10000; i ++) {
 
var p = document.createElement("p");
 
var oTxt = document.createTextNode("段落" + i);
 
p.appendChild(oTxt);
 
oFragment.appendChild(p);
 
}
 
document.body.appendChild(oFragment);

  

jQuery的 append等方法内部也是通过createDocumentFragment来实现的,最好在循环外一次性批量添加DOM元素。 

 
//不建议的做法
 
 
 
varbrowserList = ["IE", "Mozilla Firefox", "Safari", "Chrome", "Opera"];
 
 
 
$.each(browserList, function (index,value) {
 
 
 
$('<li>').text(value).appendTo($('ul').eq(0));
 
 
 
})
 
 
 
//好的做法NO1
 
 
 
varbrowserList = ["IE", "Mozilla Firefox", "Safari", "Chrome", "Opera"];
 
 
 
varmyHTML = '';
 
 
 
$.each(browserList, function (index, value) {
 
 
 
myHTML += '<li>' + value + '</li>';
 
 
 
})
 
 
 
$('ul').eq(0).html(myHTML);
 
 
 
//好的作法NO2
 
 
 
var frag= document.createDocumentFragment();
 
 
 
varbrowserList = ["IE", "Mozilla Firefox", "Safari", "Chrome", "Opera"];
 
 
 
$.each(browserList, function (index, value) {
 
 
 
varli = document.createElement("li");
 
 
 
li.textContent = value;
 
 
 
frag.appendChild(li);
 
 
 
})
 
 
 
$('ul').eq(0).append($(frag));

  

2.      使用display:none,先隐藏后显示,只会引起两次reflow和repaint。因display:none的元素不在render tree,对其操作不会引起其他元素的reflow和repaint。

3.      使用cloneNode和replaceChild,引发一次reflow和repaint。

 

阻塞渲染:CSS 与 JavaScript

谈论资源的阻塞时,我们要清楚,现代浏览器总是并行加载资源。例如,当 HTML 解析器(HTML Parser)被脚本阻塞时,解析器虽然会停止构建 DOM,但仍会识别该脚本后面的资源,并进行预加载。

同时,由于下面两点:

1. 默认情况下,CSS 被视为阻塞渲染的资源,这意味着浏览器将不会渲染任何已处理的内容,直至 CSSOM 构建完毕。

2. JavaScript 不仅可以读取和修改 DOM 属性,还可以读取和修改 CSSOM 属性。

存在阻塞的 CSS 资源时,浏览器会延迟 JavaScript 的执行和 DOM 构建。另外:

1. 当浏览器遇到一个 script 标记时,DOM 构建将暂停,直至脚本完成执行。

2. JavaScript 可以查询和修改 DOM 与 CSSOM。

3. CSSOM 构建时,JavaScript 执行将暂停,直至 CSSOM 就绪。

所以,script 标签的位置很重要。实际使用时,可以遵循下面两个原则:

1. CSS 优先:引入顺序上,CSS 资源先于 JavaScript 资源。

2. JavaScript 应尽量少影响 DOM 的构建。

浏览器的发展日益加快(目前的 Chrome 官方稳定版是 61),具体的渲染策略会不断进化,但了解这些原理后,就能想通它进化的逻辑。下面来看看 CSS 与 JavaScript 具体会怎样阻塞资源。

CSS

<style>p{color:red;}</style>
<link rel="stylesheet" href="index.css">

  

这样的 link 标签(无论是否 inline)会被视为阻塞渲染的资源,浏览器会优先处理这些 CSS 资源,直至 CSSOM 构建完毕。

渲染树(Render-Tree)的关键渲染路径中,要求同时具有 DOM 和 CSSOM,之后才会构建渲染树。即,HTML 和 CSS 都是阻塞渲染的资源。HTML 显然是必需的,因为包括我们希望显示的文本在内的内容,都在 DOM 中存放,那么可以从CSS 上想办法。

最容易想到的当然是精简 CSS 并尽快提供它。除此之外,还可以用媒体类型(media type)和媒体查询(media query)来解除对渲染的阻塞。

<link href="index.css" rel="stylesheet">
<link href="print.css" rel="stylesheet" media="print">
<link href="other.css" rel="stylesheet" media="(min-width: 30em) and (orientation: landscape)">

  

第一个资源会加载并阻塞。
第二个资源设置了媒体类型,会加载但不会阻塞,print 声明只在打印网页时使用。
第三个资源提供了媒体查询,会在符合条件时阻塞渲染。

JavaScript

JavaScript的情况比 CSS 要更复杂一些。观察下面的代码:

<p>Do not go gentle into that good night,</p>
<script>console.log("inline")</script>
<p>Old age should burn and rave at close of day;</p>
<script src="app.js"></script>
<p>Rage, rage against the dying of the light.</p>
 
<p>Do not go gentle into that good night,</p>
<script src="app.js"></script>
<p>Old age should burn and rave at close of day;</p>
<script>console.log("inline")</script>
<p>Rage, rage against the dying of the light.</p>

  

这样的 script 标签会阻塞 HTML 解析,无论是不是 inline-script。上面的 P 标签会从上到下解析,这个过程会被两段JavaScript 分别打断一次(加载并且执行的时间段内)。

所以实际工程中,我们常常将资源放到文档底部。

改变阻塞模式:defer 与 async

为什么要将 script 加载的 defer 与 async 方式放到后面呢?因为这两种方式是的出现,全是由于前面讲的那些阻塞条件的存在。换句话说,defer 与 async 方式可以改变之前的那些阻塞情形。

首先,注意 async 与 defer 属性对于 inline-script 都是无效的,所以下面这个示例中三个 script 标签的代码会从上到下依次执行。

<!-- 按照从上到下的顺序输出 1 2 3 -->
<script async>
  console.log("1");
</script>
<script defer>
  console.log("2");
</script>
<script>
  console.log("3");
</script>

  

 

故,下面两节讨论的内容都是针对设置了 src 属性的 script 标签。

defer

<script src="app1.js" defer></script>
<script src="app2.js" defer></script>
<script src="app3.js" defer></script>

  

defer属性表示延迟执行引入的 JavaScript,即这段 JavaScript 加载时 HTML 并未停止解析,这两个过程是并行的。整个document 解析完毕且 defer-script 也加载完成之后(这两件事情的顺序无关),会执行所有由 defer-script 加载的JavaScript 代码,然后触发 DOMContentLoaded 事件。

defer不会改变 script 中代码的执行顺序,示例代码会按照 1、2、3 的顺序执行。所以,defer 与相比普通 script,有两点区别:载入 JavaScript 文件时不阻塞 HTML 的解析,执行阶段被放到 HTML 标签解析完成之后。

async

<script src="app.js" async></script>
<script src="ad.js" async></script>
<script src="statistics.js" async></script>

  

async属性表示异步执行引入的 JavaScript,与 defer 的区别在于,如果已经加载好,就会开始执行——无论此刻是HTML 解析阶段还是 DOMContentLoaded 触发之后。需要注意的是,这种方式加载的 JavaScript 依然会阻塞 load 事件。换句话说,async-script 可能在 DOMContentLoaded 触发之前或之后执行,但一定在 load 触发之前执行。

从上一段也能推出,多个 async-script 的执行顺序是不确定的。值得注意的是,向 document 动态添加 script 标签时,async 属性默认是 true,下一节会继续这个话题。

document.createElement

使用 document.createElement 创建的 script 默认是异步的,示例如下。

console.log(document.createElement("script").async);// true

  

所以,通过动态添加 script 标签引入 JavaScript 文件默认是不会阻塞页面的。如果想同步执行,需要将 async 属性人为设置为 false。

如果使用 document.createElement 创建 link 标签会怎样呢?

conststyle=document.createElement("link");
style.rel="stylesheet";
style.href="index.css";
document.head.appendChild(style);// 阻塞?

  

其实这只能通过试验确定,已知的是,Chrome 中已经不会阻塞渲染,Firefox、IE 在以前是阻塞的,现在会怎样我没有试验。

document.write 与 innerHTML

通过 document.write 添加的 link 或 script 标签都相当于添加在 document 中的标签,因为它操作的是 document stream(所以对于 loaded 状态的页面使用 document.write 会自动调用 document.open,这会覆盖原有文档内容)。即正常情况下, link 会阻塞渲染,script 会同步执行。不过这是不推荐的方式,Chrome 已经会显示警告,提示未来有可能禁止这样引入。如果给这种方式引入的 script 添加 async 属性,Chrome 会检查是否同源,对于非同源的 async-script 是不允许这么引入的。

如果使用 innerHTML 引入 script 标签,其中的 JavaScript 不会执行。当然,可以通过 eval() 来手工处理,不过不推荐。如果引入 link 标签,我试验过在 Chrome 中是可以起作用的。另外,outerHTML、insertAdjacentHTML() 应该也是相同的行为,我并没有试验。这三者应该用于文本的操作,即只使用它们添加 text 或普通 HTML Element。

  

参考:

http://www.cnblogs.com/Peng2014/p/4687218.html

https://segmentfault.com/a/1190000014070240

https://developers.google.com/web/fundamentals/performance/critical-rendering-path/

https://zhuanlan.zhihu.com/p/29418126 

Guess you like

Origin www.cnblogs.com/chargeworld/p/11221698.html