回流和重绘——周分享

浏览器的回流与重绘 (Reflow & Repaint)

image.png
image.png
  • 浏览器使用流式布局模型 (Flow Based Layout)。指的就是处于流中靠后位置元素通常不会影响靠前位置元素的几何特征,因此布局可以按从左至右、从上至下的顺序遍历文档。对Render Tree的计算通常只需要遍历一次就可以完成。但是也有例外情况,比如 HTML 表格的计算就需要不止一次的遍历。

  • 浏览器会把HTML解析成DOM,把CSS解析成CSSOM,DOM和CSSOM合并就产生了Render Tree。

  • 有了RenderTree,我们就知道了所有节点的样式,然后计算他们在页面上的大小和位置,最后把节点绘制到页面上。

一句话:回流必将引起重绘,重绘不一定会引起回流。就比如改变个div的背景颜色并不会触发回流,可是改变了浏览器窗口大小或者改变了这个div的宽度或位置就触发了回流。


回流

通过构造渲染树(Render Tree),我们将可见DOM节点以及它对应的样式结合起来,可是我们还需要计算它们在设备视口内的确切位置和大小,这个计算的阶段就是回流

重绘

通过构造渲染树(Render Tree)和回流阶段,我们知道了哪些节点是可见的,以及可见节点的样式和具体的几何信息(位置、大小),那么我们就可以将渲染树的每个节点都转换为屏幕上的实际像素,这个阶段就叫做重绘节点。

当代浏览器的优化
  • 回流比重绘的代价要更高。
    有时即使仅仅回流一个单一的元素,它的父元素以及任何跟随它的元素也会产生回流。
clientWidth、clientHeight、clientTop、clientLeft
offsetWidth、offsetHeight、offsetTop、offsetLeft
scrollWidth、scrollHeight、scrollTop、scrollLeft
width、height
getComputedStyle()
getBoundingClientRect()

正常浏览器会维护一个队列,把所有引起回流和重绘的操作放入队列中,如果队列中的任务数量或者时间间隔达到一个阈值的,浏览器就会将队列清空,进行一次批处理,这样可以把多次回流和重绘变成一次。
当你访问上述的属性或方法时,队列中可能会有影响到这些属性或方法返回值的操作,浏览器便会强行清空队列,确保你拿到的值是最精确的。

  • 最小化回流和重绘
const el = document.getElementById('element');
el.style.padding = '2px';
el.style.borderLeft = '2px';
el.style.width = '30px';
·········

有多个样式属性被修改了,每一个都会影响元素的几何结构,引起回流、有其他代码访问了布局信息(上文中的会触发回流的布局信息),那么就会导致多次重绘。因此,可以通过添加整个CSS的类来合并所有的改变然后依次处理。


const el = document.getElementById('element');
el.className += ' active';


const el = document.getElementById('test');
  • css contain 属性允许当前元素和它的内容尽可能的独立于 DOM 树的其他部分。隔离指定节点的样式、布局和渲染。

  • 元素设置display: none,操作结束后再把它显示出来。因为在display属性为none的元素上进行的DOM操作不会引发回流和重绘。避免频繁读取会引发回流/重绘的属性。

  • 对具有复杂动画的元素使用绝对定位,使它脱离文档流,在文档流中的动画会引起父元素及后续元素频繁回流。而比起考虑如何减少回流重绘,我们更期望的是,根本不要回流重绘。所以css3硬件加速(GPU加速)才是我们所期望的。 使用transform、opacity、filters等这些属性是不会引起回流重绘 。

  • 避免触发同步布局事件

function test() {
    for (let i = 0; i < 100; i++) {
        i.style.width = `${box.offsetWidth}px`;
    }
}

这段代码看上去是没有什么问题,可是其实会造成很大的性能问题。立即回流是同步的在每次循环的时候,都读取了box的一个offsetWidth属性值,然后利用它来更新标签的width属性。这就导致了每一次循环的时候,都会强制浏览器刷新队列。才能响应本次循环的样式读取操作。

const width = box.offsetWidth;
function test() {
    for (let i = 0; i < 100; i++) {
        i.style.width =`${width}px';
    }
}

可以定义一个变量来缓存box的offsetWidth属性值,避免循环中回流。

总结一下:
会引起元素位置变化的就会回流(Reflow),如窗口大小改变、字体大小改变、以及元素位置改变,都会引起周围的元素改变他们以前的位置;
不会引起位置变化的,只是在以前的位置进行改变背景颜色等,只会重绘(Repaint);

你可能感兴趣的:(回流和重绘——周分享)