浏览器渲染机制

以前大概知道个流程,如今梳理下 印象深入。javascript

要了解浏览器渲染页面的过程,首先得知道一个名词——关键渲染路径。关键渲染路径是指浏览器从最初接收请求来的HTML、CSS、javascript等资源,而后解析、构建树、渲染布局、绘制,最后呈现给客户能看到的界面这整个过程。
用户看到页面实际上能够分为两个阶段:页面内容加载完成和页面资源加载完成,分别对应于DOMContentLoadedLoadcss

  • DOMContentLoaded事件触发时,仅当DOM加载完成,不包括样式表,图片等
  • load事件触发时,页面上全部的DOM,样式表,脚本,图片都已加载完成,  load事件时间上比$(document).ready()还后面

浏览器渲染的过程主要包括如下五步:html

  1. 浏览器将获取的HTML文档解析成DOM树。
  2. 处理CSS标记,构成层叠样式表模型CSSOM(CSS Object Model)。
  3. 将DOM和CSSOM合并为渲染树(rendering tree)将会被建立,表明一系列将被渲染的对象。
  4. 渲染树的每一个元素包含的内容都是计算过的,它被称之为布局layout。浏览器使用一种流式处理的方法,只须要一次绘制操做就能够布局全部的元素。
  5. 将渲染树的各个节点绘制到屏幕上,这一步被称为绘制painting

须要注意的是,以上五个步骤并不必定一次性顺序完成,好比DOM或CSSOM被修改时,亦或是哪一个过程会重复执行,这样才能计算出哪些像素须要在屏幕上进行从新渲染。而在实际状况中,JavaScript和CSS的某些操做每每会屡次修改DOM或者CSSOM。java

 
webkit渲染引擎流程
 

构建DOM树

当浏览器接收到服务器响应来的HTML文档后,会遍历文档节点,生成DOM树。
须要注意如下几点:node

  • DOM树在构建的过程当中可能会被CSS和JS的加载而执行阻塞
  • display:none的元素也会在DOM树中
  • 注释也会在DOM树中
  • script标签会在DOM树中

不管是DOM仍是CSSOM,都是要通过Bytes→characters→tokens→nodes→objectmodel这个过程。linux

 
 

当前节点的全部子节点都构建好后才会去构建当前节点的下一个兄弟节点。web

构建CSSOM规则树

浏览器解析CSS文件并生成CSSOM,每一个CSS文件都被分析成一个StyleSheet对象,每一个对象都包含CSS规则。CSS规则对象包含对应于CSS语法的选择器和声明对象以及其余对象。
在这个过程须要注意的是:后端

  • CSS解析能够与DOM解析同时进行。
  • CSS解析与script的执行互斥 。
  • 在Webkit内核中进行了script执行优化,只有在JS访问CSS时才会发生互斥。

构建渲染树(Render Tree)

经过DOM树和CSS规则树,浏览器就能够经过它两构建渲染树了。浏览器会先从DOM树的根节点开始遍历每一个可见节点,而后对每一个可见节点找到适配的CSS样式规则并应用。
有如下几点须要注意:浏览器

  • Render Tree和DOM Tree不彻底对应
  • display: none的元素不在Render Tree中
  • visibility: hidden的元素在Render Tree中
 
 

渲染树生成后,仍是没有办法渲染到屏幕上,渲染到屏幕须要获得各个节点的位置信息,这就须要布局(Layout)的处理了。缓存

渲染树布局(layout of the render tree)

布局阶段会从渲染树的根节点开始遍历,因为渲染树的每一个节点都是一个Render Object对象,包含宽高,位置,背景色等样式信息。因此浏览器就能够经过这些样式信息来肯定每一个节点对象在页面上的确切大小和位置,布局阶段的输出就是咱们常说的盒子模型,它会精确地捕获每一个元素在屏幕内的确切位置与大小。须要注意的是:

  • float元素,absoulte元素,fixed元素会发生位置偏移。
  • 咱们常说的脱离文档流,其实就是脱离Render Tree。

渲染树绘制(Painting the render tree)

在绘制阶段,浏览器会遍历渲染树,调用渲染器的paint()方法在屏幕上显示其内容。渲染树的绘制工做是由浏览器的UI后端组件完成的。

浏览器渲染网页的那些事儿

浏览器主要组件结构

 
浏览器主要组件

渲染引擎主要有两个:webkit和Gecko
Firefox使用Geoko,Mozilla自主研发的渲染引擎。Safari和Chrome都使用webkit。Webkit是一款开源渲染引擎,它原本是为linux平台研发的,后来由Apple移植到Mac及Windows上。
虽然主流浏览器渲染过程叫法有区别,可是主要流程仍是相同的。

渲染阻塞

JS能够操做DOM来修改DOM结构,能够操做CSSOM来修改节点样式,这就致使了浏览器在遇到<script>标签时,DOM构建将暂停,直至脚本完成执行,而后继续构建DOM。若是脚本是外部的,会等待脚本下载完毕,再继续解析文档。如今能够在script标签上增长属性defer或者async。脚本解析会将脚本中改变DOM和CSS的地方分别解析出来,追加到DOM树和CSSOM规则树上。

每次去执行JavaScript脚本都会严重地阻塞DOM树的构建,若是JavaScript脚本还操做了CSSOM,而正好这个CSSOM尚未下载和构建,浏览器甚至会延迟脚本执行和构建DOM,直至完成其CSSOM的下载和构建。因此,script标签的位置很重要。

JS阻塞了构建DOM树,也阻塞了其后的构建CSSOM规则树,整个解析进程必须等待JS的执行完成才可以继续,这就是所谓的JS阻塞页面。

因为CSSOM负责存储渲染信息,浏览器就必须保证在合成渲染树以前,CSSOM是完备的,这种完备是指全部的CSS(内联、内部和外部)都已经下载完,并解析完,只有CSSOM和DOM的解析彻底结束,浏览器才会进入下一步的渲染,这就是CSS阻塞渲染。

CSS阻塞渲染意味着,在CSSOM完备前,页面将一直处理白屏状态,这就是为何样式放在head中,仅仅是为了更快的解析CSS,保证更快的首次渲染。

须要注意的是,即使你没有给页面任何的样式声明,CSSOM依然会生成,默认生成的CSSOM自带浏览器默认样式。

当解析HTML的时候,会把新来的元素插入DOM树里面,同时去查找CSS,而后把对应的样式规则应用到元素上,查找样式表是按照从右到左的顺序去匹配的。

例如:div p {font-size: 16px},会先寻找全部p标签并判断它的父标签是否为div以后才会决定要不要采用这个样式进行渲染)。
因此,咱们平时写CSS时,尽可能用idclass,千万不要过渡层叠。

回流和重绘(reflow和repaint)

咱们都知道HTML默认是流式布局的,但CSS和JS会打破这种布局,改变DOM的外观样式以及大小和位置。所以咱们就须要知道两个概念:replaintreflow

reflow(回流)

当浏览器发现布局发生了变化,这个时候就须要倒回去从新渲染,你们称这个回退的过程叫reflowreflow会从html这个root frame开始递归往下,依次计算全部的结点几何尺寸和位置,以确认是渲染树的一部分发生变化仍是整个渲染树。reflow几乎是没法避免的,由于只要用户进行交互操做,就势必会发生页面的一部分的从新渲染,且一般咱们也没法预估浏览器到底会reflow哪一部分的代码,由于他们会相互影响。

repaint(重绘)

repaint则是当咱们改变某个元素的背景色、文字颜色、边框颜色等等不影响它周围或内部布局的属性时,屏幕的一部分要重画,可是元素的几何尺寸和位置没有发生改变。

须要注意的是,display:none会触发reflow,而visibility: hidden属性则并不算是不可见属性,它的语义是隐藏元素,但元素仍然占据着布局空间,它会被渲染成一个空框。因此visibility:hidden只会触发repaint,由于没有发生位置变化。

另外有些状况下,好比修改了元素的样式,浏览器并不会马上reflowrepaint一次,而是会把这样的操做积攒一批,而后作一次reflow,这又叫异步reflow或增量异步reflow。可是在有些状况下,好比resize窗口,改变了页面默认的字体等。对于这些操做,浏览器会立刻进行reflow

引发reflow

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

  • 页面第一次渲染(初始化)
  • DOM树变化(如:增删节点)
  • Render树变化(如:padding改变)
  • 浏览器窗口resize
  • 获取元素的某些属性

浏览器为了得到正确的值也会提早触发回流,这样就使得浏览器的优化失效了,这些属性包括offsetLeft、offsetTop、offsetWidth、offsetHeight、 scrollTop/Left/Width/Height、clientTop/Left/Width/Height、调用了getComputedStyle()

引发repaint

reflow回流一定引发repaint重绘,重绘能够单独触发。
背景色、颜色、字体改变(注意:字体大小发生变化时,会触发回流)

减小reflow、repaint触发次数

  • transform作形变和位移能够减小reflow
  • 避免逐个修改节点样式,尽可能一次性修改
  • 使用DocumentFragment将须要屡次修改的DOM元素缓存,最后一次性append到真实DOM中渲染
  • 能够将须要屡次修改的DOM元素设置display:none,操做完再显示。(由于隐藏元素不在render树内,所以修改隐藏元素不会触发回流重绘)
  • 避免屡次读取某些属性
  • 经过绝对位移将复杂的节点元素脱离文档流,造成新的Render Layer,下降回流成本

几条关于优化渲染效率的建议

结合上文有如下几点能够优化渲染效率。

  • 合法地去书写HTML和CSS ,且不要忘了文档编码类型。
  • 样式文件应当在head标签中,而脚本文件在body结束前,这样能够防止阻塞的方式。
  • 简化并优化CSS选择器,尽可能将嵌套层减小到最小。
  • DOM 的多个读操做(或多个写操做),应该放在一块儿。不要两个读操做之间,加入一个写操做。
  • 若是某个样式是经过重排获得的,那么最好缓存结果。避免下一次用到的时候,浏览器又要重排。
  • 不要一条条地改变样式,而要经过改变class,或者csstext属性,一次性地改变样式。
  • 尽可能用transform来作形变和位移
  • 尽可能使用离线DOM,而不是真实的网面DOM,来改变元素样式。好比,操做Document Fragment对象,完成后再把这个对象加入DOM。再好比,使用cloneNode()方法,在克隆的节点上进行操做,而后再用克隆的节点替换原始节点。
  • 先将元素设为display: none(须要1次重排和重绘),而后对这个节点进行100次操做,最后再恢复显示(须要1次重排和重绘)。这样一来,你就用两次从新渲染,取代了可能高达100次的从新渲染。
  • position属性为absolutefixed的元素,重排的开销会比较小,由于不用考虑它对其余元素的影响。
  • 只在必要的时候,才将元素的display属性为可见,由于不可见的元素不影响重排和重绘。另外,visibility : hidden的元素只对重绘有影响,不影响重排。
    使用window.requestAnimationFrame()window.requestIdleCallback()这两个方法调节从新渲染。
 
 
相关文章
相关标签/搜索