这是我参与更文挑战的第8天,活动详情查看: 更文挑战
阅读浏览器工作原理(四)
布局
布局是递归过程,从根呈现器(对应html元素,尺寸为浏览器可见区域,window.innerWidth/Height)开始递归遍历层次,并调用子代的layout方法进行布局,这时会为每个需要计算的呈现器计算几何信息。
htm采用基于流的布局模型,所以大多数情况下只需要从上往下,从左往右的顺序一次遍历即可。但是也有例外,html表格的计算就需要不止一次的遍历。
全局布局和增量布局
-
全局布局
触发了整个呈现树的布局,往往是同步触发的,如:
- 全局样式更改:如字体大小更改
- 屏幕大小调整、页面发生滚动。
-
增量布局
浏览器采用dirty位系统,对发生了更改的呈现进行标记。如果有子元素发生更改则标记为children are dirty,如果是元素本身发生更改则标记为dirty。
增量布局可能是异步触发的,例如经过网络请求后有额外的内容添加到dom树中,导致生成新的呈现器。firefox会将reflow命令保存到队列,再由调度程序批量执行,用合并命令的方式减少操作。webkit会采用定时器的方式,遍历呈现器并对dirty呈现器布局。
也可能是同步触发的,例如脚本请求样式信息(.offsetHeight),浏览器为了获取最新的值会应用所有的更改,强制刷新渲染队列。
浏览器的优化措施
- 如果是调整了浏览器视口的大小或者是呈现器的位置触发重排,就可以从缓存中获取呈现器大小,不用重新计算。
- 如果只有一个子树进行了修改就不用触发根节点的布局。例如在文本中插入文字。
布局流程
-
父呈现器确认自身宽度
如果是以下html代码:
<div style="width: 30%"></div>- 计算父容器宽度availableWidth:clientWidth()-paddingLeft()-paddingRight(),最后取max(availableWidth, 0)
- 计算元素宽度:availableWidth*30%,再加上边距和边框。
- 判断是否有设置max/min-width,取min(元素宽度,max-width)或max(元素宽度,min-width)。
-
处理子呈现器
- 设置其坐标
- 如果子呈现器是dirty的,或者是全局布局,就调用其layout。
-
父呈现器根据子呈现器的新高度+边距来设置自身高度
-
将dirty位设置为false
换行
如果呈现器布局过程中需要换行,就会停止布局并告知父呈现器需要换行。父呈现器会再创建一个呈现器,并调用其layout进行布局。
绘制
操作系统遍历呈现树并且调用呈现器的paint方法,由用户界面后端将呈现器内容显示在屏幕上。
增量绘制
呈现器发生更改后,将对应的矩形区域设置为无效,操作系统就会认为是dirty区域,并且生成paint事件。操作系统还会把多个区域合并成一个。
在chrome中,呈现器分布在每个标签页的主线程上,而不是浏览器的主线程。他会侦听改变呈现器的事件,然后通知根呈现器,根呈现器遍历找到那个需要更新的呈现器,再由那个呈现器重新绘制自己和子呈现器。
绘制顺序
绘制顺序其实就是堆叠上下文顺序,也就是html内元素发生层叠的时候,元素在用户方向上的顺序。据说有css2规范定义的有点老了,这里先引用张鑫旭大佬的图,后面再开一篇文章详细讲吧:
浏览器对重绘的优化
-
firefox显示列表
遍历整个呈现树,为每个矩形设置显示列表,其中按照呈现器的背景、边框等绘制顺序,包含相关的呈现器(背景:xxx呈现器,xxx呈现器这样的吗?)。重新绘制的时候只需要再遍历一次呈现树整理一遍就好了。
-
webkit矩形存储
重新绘制前会把原来的矩形存储为位图,只绘制新矩形的不同部分。
重排重绘的触发
第一次渲染的时候必然会触发重排和重绘 剩下的触发重排和重绘的大概情况有:
-
重排
- 改变窗口大小
- 改变文字大小
- 增加或移除样式表
- 内容的改变 例如输入框输入文字
- 激活伪类 如:hover
- 操作class属性
- 脚本操作DOM增删改查
- 计算offsetWidth和offsetHeight
- 设置style属性
-
重绘
- 只是改变某个元素的背景色
- 文字颜色
- 边框颜色
- 主要就是不影响它周围或内部布局的属性