回流和重绘是浏览器渲染的机制,是经过服务器Response响应阶段之后进行的渲染机制,相应要了解回流和重绘先看一下浏览器的渲染机制。
浏览器的解析过程
- 当浏览器拿到一个网页后,首先浏览器会先解析HTML,如果遇到了外链的css,会一下载css,一边解析HTML。
- 当css下载完成后,会继续解析css,生成css Rules tree,不会影响到HTML的解析。
- 当遇到script标签时,一旦发现有对javascript的引用,就会立即下载脚本,同时阻断文档的解析,等脚本执行完成后,再开始文档的解析。
浏览器的渲染过程
浏览器渲染过程如下:
1.解析HTML,生成DOM树,解析CSS,生成CSSOM树
2.将DOM树和CSSOM树结合,生成渲染树(Render Tree)
3.Layout(回流)
:根据生成的渲染树,进行回流(Layout),得到节点的几何信息(位置,大小)
4.Painting(重绘)
:根据渲染树以及回流得到的几何信息,得到节点的绝对像素
5.Display:将像素发送给GPU,展示在页面上。(这一步其实还有很多内容,比如会在GPU将多个合成层合并为同一个层,并展示在页面中。而css3硬件加速的原理则是新建合成层,这里我们不展开,之后有机会会写一篇博客)
渲染过程看起来很简单,让我们来具体了解下每一步具体做了什么。
- 生成渲染树
1.从DOM树的根节点开始遍历每个可见节点。
2.对于每个可见的节点,找到CSSOM树中对应的规则,并应用它们。
3.根据每个可见节点以及其对应的样式,组合生成渲染树。
第一步中,既然说到了要遍历可见的节点,那么我们得先知道,什么节点是不可见的。不可见的节点包括: 一些不会渲染输出的节点,比如script、meta、link等。 一些通过css进行隐藏的节点。比如display:none。注意,利用visibility和opacity隐藏的节点,还是会显示在渲染树上的。只有display:none的节点才不会显示在渲染树上。
注意: 渲染树只包含可见的节点
不同内核的浏览器渲染
上图是webkit内核的渲染流程,和总体渲染流程差不多,要构建HTML的DOM Tree,和CSS规则树,然后合并生成Render Tree,最后渲染。 这个是Mozilla的Gecko渲染引擎。总体看来渲染流程差不多,只不过在生成渲染树或者Frame树时,两者叫法不一致,webkit称之为Layout,Gecko叫做Reflow。
回流reflow&重绘repaints
回流:当Render Tree中部分或全部元素的尺寸、结构、或某些属性发生改变时,浏览器重新渲染部分或全部文档的过程称为回流。
重绘:当页面中元素样式的改变并不影响b布局时(eg:color、background-color等),浏览器会将新样式赋予给元素并重新绘制它(但宽高、大小、位置等不变),这个过程称为重绘。
何时发生
- 页面首次加载
- 删除,增加,或者修改DOM元素节点。
- 移动DOM的位置,开启动画的时候。
- 修改CSS样式,改变元素的大小,位置时,或者将使用display:none时,会造成重排;修改CSS颜色或者visibility:hidden等等,会造成重绘。
- 修改网页的默认字体时。
- Resize窗口的时候(移动端没有这个问题),或是滚动的时候。
- 内容的改变,(用户在输入框中写入内容也会)。
- 激活伪类,如:hover。
- 计算offsetWidth和offsetHeight。
- 调整窗口大小
- 字体大小
- 一些常用且会导致回流的属性和方法。
- offsetTop, offsetLeft, offsetWidth, offsetHeight
- scrollTop/Left/Width/Height
- clientTop/Left/Width/Height
- IE中的 getComputedStyle(), 或 currentStyle
如何避免浏览器的回流&重绘
- 放弃操作dom,基于vue/react的数据影响视图模式
- 分离读写操作(利用现代浏览器的渲染队列机制)
box.style.width = '200px'
box.style.hight = '200px'
这是一次线进城
box.style.width = '200px'
box.style.hight = '200px'
console.log(div)
这是两次线进城
将样式进行集中处理,其中一下会刷新渲染队列 offsetTop、offsetLeft、offsetWidth、offsetHeight、clientTop、clientLeft、clientWidth、clientHeight scrollTop、scrollLeft、scrollWidth、scrollHeight、getComputedStyle、currentStyle....
- 样式集中改变
div.style.cssText = 'width:20px;height:20px;';
div.className = 'box';
- 尽量避免style的使用,对于需要操作DOM元素节点,重新命名className,更改className名称。
- 缓存布局信息
bad
div.style.left = div.offsetLeft + 1 + 'px';
div.style.top = div.offsetTop + 1 + 'px';
better
var curLeft = div.offsetLeft; var curTop = div.offsetTop;
div.style.left = curLeft + 1 + 'px'; div.style.top = curTop + 1 + 'px';
不要经常获取同一个元素,可以第一次获取元素后,用变量保存下来,减少遍历时间
- 元素批量修改
bad
<ul id="box">
</ui>
<script>
for(let i = 0;i<5;i++){
let newLi = document.createElement('li')
newLi.innerHTML=i
box.appendChild(newLi)
}
</script>
better
let frg =document.createDocumentFragment()
fo(let i = 0;i<5;i++){
let newLi = document.createElement('li')
newLi.innerHTML=i
frg.appendChild(newLi)
}
box.appendChild(frg)
frg = null
best
// 字符串拼接
let str = ``;
for (let i = 0; i < 5; i++) {
str += `<li>${i}</li>`;
}
box.innerHTML = str;
- 动画效果应用到position属性为absolute或fixed的元素上(脱离文档流)
box.style.transform='translateX(200x)'
-
CSS3硬件加速(GPU加速)
比起考虑如何减少回流重绘,我们更期望的是,根本不要回流重绘;transform \ opacity \ filters ... 这些属性会触发硬件加速,不会引发回流和重绘......可能会引发的坑:过多使用会占用大量内存,性能消耗严重、有时候会导致字体模糊等
-
牺牲平滑度换取速度
每次1像素移动一个动画,但是如果此动画使用了100%的CPU,动画就会看上去是跳动的,因为浏览器正在与更新回流做斗争。每次移动3像素可能看起来平滑度低了,但它不会导致CPU在较慢的机器中抖动 -
避免table布局和使用css的javascript表达式
-
尽量少使用dispaly:none,可以使用visibility:hidden代替,dispaly:none会造成重排,visibility:hidden会造成重绘。
-
使用resize事件时,做防抖和节流处理。
-
批量修改元素时,可以先让元素脱离文档流,等修改完毕后,再放入文档流。