页面渲染机制
Last updated
Last updated
浏览器进行DNS域名解析,得到对应的IP地址
根据这个IP,找到对应的服务器建立连接(三次握手)
建立TCP连接后发起HTTP请求(一个完整的http请求报文)
服务器响应HTTP请求,浏览器得到html代码(服务器如何响应)
浏览器解析html代码,并请求html代码中的资源(如js、css、图片等)
浏览器对页面进行渲染呈现给用户
服务器关闭TCP连接(四次挥手)
主流的浏览器内核主要有2种,Webkit 和 Geoko ,虽然 chrome 现在的内核更换为 blink ,但其实 blink是基于webkit的,差异不大。其渲染过程分别如下:
这两个内核的渲染流程大同小异,主要的过程可以总结为下列5个:
DomTree: 解析html构建DOM树
CssdomTree : 解析CSS生成CSSOM规则树
RenderTree: 将DOM树与CSSDOM规则树合并在一起生成渲染对象树
Layout: 遍历渲染树开始布局(layout),计算每个节点的位置大小信息
Painting: 将渲染树每个节点绘制到屏幕
DOM树的构建: 浏览器在接收到html文件后即开始解析和构建DOM树,在碰到js代码段时,由于js代码可能会改变dom的结构,所以为避免重复操作,浏览器会停止dom树构建,先加载并解析js代码。而对于css,图片,视频等资源,则交由资源加载器去加载,这个过程是异步的,并不会阻碍dom树的生成。这个过程需要注意的点是:
display:none的元素、注释存在于dom树中 js会阻塞dom树的构建从而阻塞其他资源的并发加载,因此好的做法是将js放在最后加载 对于可异步加载的js片段加上 async 或 defer
CSSDOM树的构建: 浏览器在碰到<link> 和 <style> 标签时,会解析css生成cssom , 当然,link标签需要先将css文件加载完成才能解析。 需要注意的是:
js 代码会阻塞cssom的构建,在webkit内核中有所优化,只有js访问css才会阻塞 cssom的构建与dom树的构建是并行的 减少css的嵌套层级和合理的定义css选择器可以加快解析速度,可参考如何提升 CSS 选择器性能
RenderTree的构建: 在cssom 和dom 树都构建完成后,浏览器会将他们结合,生成渲染对象树,渲染树的每一个节点,包含了可见的dom节点和节点的样式 。 需要注意的是:
renderObject树 与 dom树不是完全对应的,不可见的元素如display:none 是不会放入渲染树的。
visibility: hidden的元素在Render Tree中
Layout: 这一步是浏览器遍历渲染对象树,并根据设备屏幕的信息,计算出节点的布局、位置,构建出渲染布局树(render layout)。渲染布局树输出的就是我们常说的盒子模型,需要注意的是:
float, absolute , fixed 的元素的位置会发生偏移
我们常说的脱离文档流,其实就是脱离布局树
Painting: 浏览器对生成的布局树进行绘制,由用户界面后端层将每个节点绘制出来。此时,Webkit内核还需要将渲染结果从Renderer进程传递到Browser进程。 一句话:回流必将引起重绘,重绘不一定会引起回流。
前面讲到,js代码可以访问和修改dom节点和css,所以在解析js的过程中会导致页面重新布局和渲染,这就是重绘(repaint)和回流(reflow)
概念: 重绘是指css样式的改变,但元素的大小和尺寸不变,而导致节点的重新绘制。
重绘的触发: 任何对元素样式,如background-color、border-color、visibility
等属性的改变。css 和 js 都可能引起重绘。
回流(reflow)是指元素的大小、位置发生了改变,而导致了布局的变化,从而导致了布局树的重新构建和渲染。
回流的触发
页面首次渲染
浏览器窗口大小发生改变
元素内容变化(文字数量或图片大小等等)
元素字体大小变化
dom元素的位置和尺寸大小的变化
dom元素的增加和删除
激活CSS伪类(例如::hover)
增加和删除class样式
动态计算修改css样式
查询某些属性或调用某些方法
一些常用且会导致回流的属性和方法:
当然,我们的浏览器不会每一次reflow都立刻执行,而是会积攒一批,这个过程也被成为异步reflow,或者增量异步reflow。但是有些情况浏览器是不会这么做的,比如:resize窗口,改变了页面默认的字体,等。对于这些操作,浏览器会马上进行reflow
浏览器会维护一个队列,把所有引起回流和重绘的操作放入队列中,如果队列中的任务数量或者时间间隔达到一个阈值的,浏览器就会将队列清空,进行一次批处理,这样可以把多次回流和重绘变成一次。 当你访问以下属性或方法时,浏览器会立刻清空队列:
因为队列中可能会有影响到这些属性或方法返回值的操作,即使你希望获取的信息与队列中操作引发的改变无关,浏览器也会强行清空队列,确保你拿到的值是最精确的
避免使用table布局。
尽可能在DOM树的最末端改变class。
避免设置多层内联样式。
将动画效果应用到position属性为absolute或fixed的元素上。
避免使用CSS表达式(例如:calc())
避免频繁操作样式,最好一次性重写style属性,或者将样式列表定义为class并一次性更改class属性。
避免频繁操作DOM,创建一个documentFragment,在它上面应用所有DOM操作,最后再把它添加到文档中。
也可以先为元素设置display: none,操作结束后再把它显示出来。因为在display属性为none的元素上进行的DOM操作不会引发回流和重绘。
避免频繁读取会引发回流/重绘的属性,如果确实需要多次使用,就用一个变量缓存起来。
对具有复杂动画的元素使用绝对定位,使它脱离文档流,否则会引起父元素及后续元素频繁回流。
使用chrome浏览器的开发者工具,我们很容易看到这5个过程的时间线,下面是segmentfault主页的渲染截图: