背景知识
Chrome 网页生成过程中,主要包含如下几个步骤:
我们可以通过避免大量重排(reLayout)、重绘(rePaint)引起掉帧以达到性能优化的目的。
什么是重排和重绘?
Layout:
重排是浏览器计算各元素几何信息的过程:元素的大小以及在页面中的位置。 根据所用的 CSS、元素的内容或父级元素,每个元素都将有显式或隐含的大小信息。此过程在 Chrome、Opera、Safari 和 Internet Explorer 中称为布局 (Layout)。 在 Firefox 中称为自动重排 (Reflow),但实际上其过程是一样的。
触发Layout:
当您更改样式时,浏览器会检查任何更改是否需要计算布局,以及是否需要更新渲染树。对“几何属性”(width,height,top,left......)的更改都需要布局计算。
⭐note:布局几乎总是作用到整个文档。 如果有大量元素,将需要很长时间来算出所有元素的位置和尺寸。
Layout 优化方案:
- 使用Flexbox布局模型比基于浮动的布局模型的性能更快。
- 精简DOM节点数量。
- 读写分离先获取元素的样式值(浏览器可以使用上一帧的布局值),然后在进行元素的样式修改。 看看这个代码:
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:
除 transform 或 opacity 属性之外,更改任何属性始终都会触发绘制。
优化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:不要创建太多层,因为每层都需要内存和管理开销。
优化总结
- 使用 Flexbox 布局模型比基于浮动的布局模型的性能更快。
- 精简 DOM 节点数量。
- 读写分离先获取元素的样式值(浏览器可以使用上一帧的布局值),然后在进行元素的样式修改
- 如果反复进行重绘和重排可能会导致掉帧,这是因为有可能JS执行阻塞了主线程;
- 使用 CSS3 中的
transform,opacity属性实现动画不会经过布局和绘制,而是直接运行在合成器线程和栅格化线程中,不会受到主线程的影响。
参考资料:触发layout和paint的css属性 csstriggers.com