浏览器渲染原理

4 阅读4分钟

浏览器的网络进程拿到一个html之后生成一个渲染任务放在消息队列,渲染主线程从消息队列拿到这个任务后开始渲染

渲染过程

渲染步骤: 解析html,样式计算,布局,分层,绘制,分块,光栅化,合成

一.解析html:
  1. 渲染主线程在解析前会启动一个预解析线程,进行下载需要用到的外部css和外部js
  2. 解析过程中遇到css解析css,遇到js停下来执行js
  3. 一边解析一边生成DOM树和CSSOM树,CSSOM中包括link引入的外部样式,内联样式,内部样式,和浏览器默认样式
  4. 解析过程中遇到link,这时候如果外部css还没下载好会直接跳过异步下载完之后再解析,但是如果遇到script会停下来等待脚本下载完之后执行js,这是因为js可能会影响DOM树解构,所以js会阻塞解析进程,解析完当前js之后继续执行后面的解析
  5. 解析html进行完之后会得到dom树和cssom树
二.样式计算:

主线程会遍历得到的dom树,为每个节点计算最终的样式,会把一些预设值改为绝对值,比如vh,em,50%都改成px,注意:最终每个dom节点会关联一个样式对象,但是dom树本身不带计算后样式(注意,内联样式和class这些是原样写在dom上的但不会被计算,CSSOM上存的是计算后样式)

三.布局:

依次遍历dom树每个节点根据dom树和每个节点关联的样式对象计算得到layout树

四.分层:

浏览器会用一套复杂的策略进行分层,因为当用户进行交互后页面可能会出现变化,这时候为了不需要整个页面重新绘制,会把页面分层,当某一层的样式改变的时候只重绘该层,其他层不受影响,分层可以优化重绘和合成,但是会有额外的内存和性能开销,需要权衡,注意:分层不是一个固定死的阶段,而是动态的,随时可能出现的,注意可以将重绘限制在单个图层中,但是回流通常涉及多个图层位置的移动与尺寸计算,使用分层是无法优化回流的,在布局之后会进行初步的分层,然后当元素动态变化的时候浏览器会权衡分层,所以可以在一个元素即将被修改的时候用will-change提醒浏览器这个元素要进行修改(will-change:opacity;就是提醒浏览器透明度要修改了,也可以will-change:transform,opacity;修改多个属性,但是一般不这样,这样相当于一直提示分层,一般会用js控制在要改变的时候提示分层

// 在用户可能点击/悬停的区域附近时提前提示
button.addEventListener('mouseenter', () => {
  button.style.willChange = 'transform'; // 提前准备
  // 动画可能在 200ms 后由 click 触发
});

),另外如果是一次性动画结束之后记得用willchange='auto'及时清理,不过要理解willchange只是提醒浏览器要修改不是强制浏览器分层,另外 分层有额外开销不要滥用,willchange是为了优化交互体验,避免当用户进行交互触发变化的时候浏览器紧急分层修改造成卡顿,提前告诉浏览器分层,在实际交互的时候就不需要临时分层了,will-change只在复杂动画时或明显卡顿建议使用

五.绘制:

主线程对每一层单独产生绘制绘制指令集绘制

-----------接下来的分块和光栅化在合成线程中不在主线程中

六.分块:

合成线程把每一层分层更小的小块(从线程池拿出更多线程做这一步)

七.光栅化:

合成线程将块信息交给GPU进行光栅化,得到最终的位图

八.合成:

合成线程拿到位图后生成一个个quad(指引)信息,标识每个位图画在哪里,以及旋转等变形,最终提交给GPU由GPU绘制 ,transform就是在合成阶段合成线程中直接更新图层的位置矩阵,所以效率高

注意事项

DOM树和Layout树不一定相同,并且都不一定等于原html结构,比如

  1. 文字只能写在行盒里,所以如果在div或p这种块盒中写文字,块盒中会添加一层匿名行盒包裹文字,而这层匿名行盒由于对布局造成影响所以在layout中,但是并不在dom中
  2. head标签不会渲染在页面上,所以layout中没有,但是dom中有
  3. 伪元素会出现在layout中不会出现在dom上(注意伪类是在cssom也有的)
  4. display:none的元素在dom上但是由于不在布局中所以不出现在layout中