前端性能优化:避免页面大量重排(reflow)与重绘(repaint)

·  阅读 848
前端性能优化:避免页面大量重排(reflow)与重绘(repaint)

背景知识

Chrome 网页生成过程中,主要包含如下几个步骤: image.png

我们可以通过避免大量重排(reLayout)、重绘(rePaint)引起掉帧以达到性能优化的目的。

什么是重排和重绘?

Layout:

重排是浏览器计算各元素几何信息的过程:元素的大小以及在页面中的位置。 根据所用的 CSS、元素的内容或父级元素,每个元素都将有显式或隐含的大小信息。此过程在 Chrome、Opera、Safari 和 Internet Explorer 中称为布局 (Layout)。 在 Firefox 中称为自动重排 (Reflow),但实际上其过程是一样的。

触发Layout:

当您更改样式时,浏览器会检查任何更改是否需要计算布局,以及是否需要更新渲染树。对“几何属性”(width,height,top,left......)的更改都需要布局计算。

⭐note:布局几乎总是作用到整个文档。 如果有大量元素,将需要很长时间来算出所有元素的位置和尺寸。

Layout 优化方案:

  1. 使用Flexbox布局模型比基于浮动的布局模型的性能更快。
  2. 精简DOM节点数量。
  3. 读写分离先获取元素的样式值(浏览器可以使用上一帧的布局值),然后在进行元素的样式修改。 看看这个代码:
function resizeAllParagraphsToMatchBlockWidth() {
  // 导致浏览器进入一个读-写-读-写的循环中。
  for (var i = 0; i < paragraphs.length; i++) {
    paragraphs[i].style.width = box.offsetWidth + 'px'; 
  }
}
复制代码

此代码循环处理一组段落,并设置每个段落的宽度以匹配一个称为“box”的元素的宽度。这看起来没有害处,但问题是循环的每次迭代读取一个样式值 (box.offsetWidth),然后立即使用此值来更新段落的宽度 (paragraphs[i].style.width)。在循环的下次迭代时,浏览器必须考虑样式已更改这一事实,因为 offsetWidth 是上次请求的(在上一次迭代中),因此它必须应用样式更改,然后运行布局。每次迭代都将出现此问题!

此示例的优化方法还是先读取值,然后写入值:

// Read.
var width = box.offsetWidth;

function resizeAllParagraphsToMatchBlockWidth() {
  for (var i = 0; i < paragraphs.length; i++) {
    // Now write.
    paragraphs[i].style.width = width + 'px';
  }
}
复制代码

Paint:

绘制是填充像素的过程,像素最终合成到用户的屏幕上。 当一个元素的外观发生改变,但没有改变布局,重新把元素外观绘制出来的过程,叫做重绘。

触发paint:

transformopacity 属性之外,更改任何属性始终都会触发绘制。

优化Paint:

创建新层的最佳方式是使用 will-change CSS 属性。此方法在 Chrome、Opera 和 Firefox 上有效,并且通过 transform 的值将创建一个新的合成器层:

.moving-element {
  will-change: transform;
}
复制代码

对于不支持 will-change 但受益于层创建的浏览器,例如 Safari 和 Mobile Safari,需要使用3D 变形来强制创建一个新层:

.moving-element {
  transform: translateZ(0);
}
复制代码

⭐note:不要创建太多层,因为每层都需要内存和管理开销。

优化总结

  1. 使用 Flexbox 布局模型比基于浮动的布局模型的性能更快。
  2. 精简 DOM 节点数量。
  3. 读写分离先获取元素的样式值(浏览器可以使用上一帧的布局值),然后在进行元素的样式修改
  4. 如果反复进行重绘和重排可能会导致掉帧,这是因为有可能JS执行阻塞了主线程;
  5. 使用 CSS3 中的transform,opacity属性实现动画不会经过布局和绘制,而是直接运行在合成器线程和栅格化线程中,不会受到主线程的影响。

参考资料:触发layout和paint的css属性 csstriggers.com

分类:
前端
收藏成功!
已添加到「」, 点击更改