// 在每一个frame开始前运行这个函数
requestAnimationFrame(logBoxHeight);
function logBoxHeight() {
// Gets the height of the box in pixels and logs it out.
console.log(box.offsetHeight);
}
如果你在问它的高度之前就改变了盒子的样式,那么问题就来了:
function logBoxHeight() {
box.classList.add('super-big');
// Gets the height of the box in pixels
// and logs it out.
console.log(box.offsetHeight);
}
function resizeAllParagraphsToMatchBlockWidth() {
// Puts the browser into a read-write-read-write cycle.
for (var i = 0; i < paragraphs.length; i++) {
paragraphs[i].style.width = box.offsetWidth + 'px';
}
}
// Read.
var width = box.offsetWidth;
function resizeAllParagraphsToMatchBlockWidth() {
for (var i = 0; i < paragraphs.length; i++) {
// Now write.
paragraphs[i].style.width = width + 'px';
}
}
原文链接:https://developers.google.com/web/fundamentals/performance/rendering/avoid-large-complex-layouts-and-layout-thrashing
前置知识
Layout与Reflow的区别是什么?
没有本质区别,只是浏览器的叫法不同。 Layout和Reflow都是浏览器处理元素几何信息的过程的叫法,几何信息包括size,location等等通过css声明的几何信息。
只不过在Chrome,Opera,Safari和IE中叫做Layout;在Firefox中叫做Reflow。
TL;DR(Too long;Don't read)
tl;dr是什么意思?
tl;dr是Too Long; Didn't Read的简写,一般用在比较长篇幅的内容前面,后面跟着一段内容的简短总结。 主要作用就是告诉读者,这篇内容篇幅比较长,如果不想深入探讨或时间有限,可以看总结。
尽可能避免layout
当你改变style时,浏览器会检查是否有依赖layout的改变去做计算,也会检查是否有渲染树需要更新。改变”几何属性“,比如width,height,left,top都依赖于layout。
Layout的作用域往往是当前document。 如果你有很多元素,会花费很长时间去找到它们的位置和维度。 如果不能避免layout的话,可以使用devtools查看 layout花费的时间,然后去判断是否是layout引起的阻塞。Performance可以看到。
老版本devtools上时timeline,看下面这张图:
layout花费了20多ms,当我们在屏幕上花费16ms得到一个frame时,就已经是算很高了。你同样可以看到,devtools将告诉你这个layout有多少个elements,以及有多少个node节点需要layout。
这里有一个查看改变属性时触发layout的网站:https://csstriggers.com/
width属性会触发layout,遭到破坏的元素需要重新绘制,然后再进行合成。
z-index虽然不会触发layout,但是会触发painting,这种操作是超级昂贵的。然后再重新合成。
新版的Performance工具:
在旧的布局模型中使用flexbox
web有很多很多layout models,其中一些兼容性更好。老的CSS layout模型允许我们在屏幕上相对定位它的位置,绝对定位和浮动定位。
下面的截图显示了使用浮动定位1300个盒子时layout的消耗。当然,这个是故意为了演示float布局的高消耗才做的。
1300个节点,float布局花了14ms。
当我们使用Flexbox布局后,1300个节点只花了3.544ms。
这种损耗对比可能不用太在意,但是大脑中一定要牢记:”布局模型的选择会影响到性能,也是一个优化点。“
任何情况下,无论是否选择Flexbox,都要牢记避免一起触发layout。
避免强制同步layout
一个frame的生成包括以下这几步:
首先是js执行,然后是样式计算,然后是布局。但是,可以用js强制浏览器更早地实现布局。 这个叫做强制同步布局(forced synchronous layout)
首先需要知道的是,由于JavaScript运行前一帧中的所有旧布局值都是已知的,可以查询到的。所以如果想在每一帧开始前写一个元素的height的话,,可以按照下面这样写:
如果你在问它的高度之前就改变了盒子的样式,那么问题就来了:
现在,为了回答高度问题,浏览器必须首先应用样式更改(因为添加了超级大的类),然后运行布局。也就是说需要执行
JavaScript -> Style->Layout
三步。因为是一个很大的class,在Style和Layout这两步可能会消耗很长的时间。 每一个选中的box都在首个frame渲染前都需要执行这一步,box一旦多起来,性能就会有明显影响了。 只有这样,它才能返回正确的高度。这是不必要的,而且可能是昂贵的工作。因此,最好在写之前读,浏览器可以用前一帧的layout值。
大多数情况下不需要赋值然后查询,用最后一帧的值就足够了。 运行样式计算和同步布局可能会导致明显的性能瓶颈。
避免layout抖动
有一种更坏的强制同步layout的方法:短时间内做大量的layout。
这段代码循环遍历一组段落,并将每个段落的宽度设置为与名为box的元素的宽度匹配。它看起来是无害的,但问题是循环的每次迭代都读取一个样式值(box.offsetWidth),然后立即读取。
解决这个问题的办法是只读一次然后写:
如果你想保证安全,你应该检查FastDOM,它会自动为你批量你的读和写,并应该防止你触发强制同步布局或布局抖动意外。