张啸


世界上最快乐的事,莫过于为理想而奋斗。


Web(6) 浏览器页面的渲染过程

浏览器在下载好HTML、CSS、JS等文件后,是如何将这些内容组装成绚丽的页面呈现给用户呢?不同的浏览器渲染过程实际上并不相同,但是依旧存在相一致的部分。

一、浏览器渲染页面

从浏览器地址栏的请求链接开始,浏览器通过DNS解析查到域名映射的IP地址,成功之后浏览器端向此IP地址取得连接。成功连接之后,浏览器端将请求头信息通过HTTP协议向此IP地址所在服务器发起请求。服务器接收到请求之后等待处理,最后向浏览器端发回响应,此时在HTTP协议下,浏览器从服务器接收到text/html类型的代码,浏览器开始显示此HTML,并获取其中内嵌资源文件,然后浏览器再发起请求来获取这些资源,并在浏览器的html中显示。

浏览器解析的大概的工作流程可以分为以下几个步骤:

  • 1) 用户输入网址(假设是HTML页面,第一次访问,无缓存情况),浏览器向服务器发出HTTP请求,服务器返回HTML文件

    优化方案:善用缓存,减少HTTP请求,减轻服务器压力

  • 2) 浏览器载入HTML代码,发现head内有一个link引用外部css文件,则浏览器立即发送css文件请求,获取浏览器返回的css文件

    优化方案:css文件合并,减少HTTP请求

  • 3) 浏览器继续载入HTML中body部分的代码,并且css文件已经拿到手了,可以开始渲染页面了

    优化方案:css文件要放在最上面,避免网页重新渲染

  • 4) 浏览器在代码中发现一个img标签引用了一张图片,向服务器发出请求,此时浏览器不会等到图片下载完,而是继续渲染后面的代码

    优化方案:图片文件合并,减少HTTP请求

  • 5) 服务器返回图片文件,由于占用了一定面积,影响后面段落的布局,因此浏览器需要回过头来重新渲染这部分代码

    优化方案:最好图片都设置尺寸,避免重新渲染

  • 6) 浏览器发现一个包含一行JavaScript的script标签,会立即运行该js代码

    优化方案:script最好放在页面最下面

  • 7) js脚本执行了语句,它令浏览器隐藏掉代码中的某个div,突然就少了一个元素,浏览器不得不重新渲染这部分代码

    优化方案:页面初始化样式不要使用js控制

  • 8) 浏览器完成渲染,展示HTML页面


二、重绘和回流

1. 什么是重绘和回流

为什么页面会慢?那是因为浏览器需要花时间去渲染,尤其是当它发现某个部分发生了点变化影响了布局,需要倒回去重新渲染,该过程称为回流(reflow)。

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

如果只是改变某个元素的背景色、文字颜色、边框颜色等等不影响它周围或内部布局的属性,将只会引起浏览器的重绘(repaint)。

repaint的速度明显快于reflow(在IE下需要换一下说法,reflow要比repaint更缓慢)。

repaint操作触发时,元素的外观被改变,且在没有改变布局的情况下发生,如改变outlinevisibilitybackground-color,不会影响到dom结构渲染。

reflowrepaint区别就是它会影响到dom的结构渲染,同时它会触发repaint,它会改变它本身与所有父辈元素,这种开销是非常昂贵的,导致性能下降是必然的,页面元素越多效果越明显。

注意:回流必将引起重绘,重绘不一定引起回流。

2. 如何减少重绘和回流

reflow是不可避免的,只能将reflow对性能的影响减少到最小

  • 1) 不要一条一条地修改DOM样式,通过设置style属性改变节点样式的话,每设置一次都会导致一次reflow。所以最好通过设置class的方式,这样可以将多次改变样式属性的操作合并成一次操作

  • 2) 让要操作的元素进行“离线处理”,处理完后一起更新

    • 使用DocumentFragment进行缓存操作,引发一次重绘与回流

      1
      2
      3
      4
      5
      6
      7
      8
      9
      var fragment = document.createDocumentFragment();

      fragment.appendChild(document.createTextNode('Hello Zhang Xiao!'));

      fragment.appendChild(document.createElement('br'));

      fragment.appendChild(document.createTextNode('Hello Leon!'));

      document.body.appendChild(fragment);
    • 使用display: none技术,只引发两次重绘与回流

      原理:由于display属性为none的元素不在渲染树中,对隐藏的元素操作不会引发其他元素的重排。如果要对一个元素进行复杂的操作时,可以先隐藏它,操作完成后再显示。这样在隐藏和显示时触发2次重排。

  • 3) 设置元素的positionabsolutefixed

元素脱离标准文档流,也从DOM树结构中脱离出来,在需要reflow时只需要reflow自身与下级元素。

  • 4) 不要使用table布局

table中某个元素一旦触发reflow就会导致table里所有的其他元素reflow。在适合用table的场合,可以设置table-layoutautofixed,这样可以让table一行一行的渲染,这种做法也是为了限制reflow的影响范围。

  • 5) 避免使用css的JavaScript表达式,如果css里有expression,每次都会重新计算一次。

参考文献

  1. 浏览器如何渲染页面