网页重排(回流)是妨碍流畅性的重要起因之一,联合 What forces layout / reflow 这篇文章与援用,整顿一下回流的起因与优化思考。
借用这张经典图:
网页渲染会经验 DOM -> CSSOM -> Layout(重排 or reflow) -> Paint(重绘) -> Composite(合成),其中 Composite 在 精读《深刻理解古代浏览器四》具体介绍过,是在 GPU 进行光栅化。
那么排除 JS、DOM、CSSOM、Composite 可能导致的性能问题外,剩下的就是咱们这次关注的重点,reflow 了。从程序上能够看进去,重排后肯定重绘,而重绘不肯定触发重排。
概述
什么时候会触发 Layout(reflow) 呢?一般来说,当元素地位发生变化时就会。但也不尽然,因为浏览器会主动合并更改,在达到某个数量或工夫后,会合并为一次 reflow,而 reflow 是渲染页面的重要一步,关上浏览器就肯定会至多 reflow 一次,所以咱们不可能防止 reflow。
那为什么要留神 reflow 导致的性能问题呢?这是因为某些代码可能导致浏览器优化生效,即明明能合并 reflow 时没有合并,这个别呈现在咱们用 js API 拜访某个元素尺寸时,为了保障拿到的是准确值,不得不提前触发一次 reflow,即使写在 for 循环里。
当然也不是每次拜访元素地位都会触发 reflow,在浏览器触发 reflow 后,所有已有元素地位都会记录快照,只有不再触发地位等变动,第二次开始拜访地位就不会触发 reflow,对于这一点会在前面具体开展。当初要解释的是,这个”触发地位等变动“,到底有哪些?
依据 What forces layout / reflow 文档的总结,一共有这么几类:
取得盒子模型信息
elem.offsetLeft
,elem.offsetTop
,elem.offsetWidth
,elem.offsetHeight
,elem.offsetParent
elem.clientLeft
,elem.clientTop
,elem.clientWidth
,elem.clientHeight
elem.getClientRects()
,elem.getBoundingClientRect()
获取元素地位、宽高的一些伎俩都会导致 reflow,不存在绕过一说,因为只有获取这些信息,都必须 reflow 能力给出精确的值。
滚动
elem.scrollBy()
,elem.scrollTo()
elem.scrollIntoView()
,elem.scrollIntoViewIfNeeded()
elem.scrollWidth
,elem.scrollHeight
elem.scrollLeft
,elem.scrollTop
拜访及赋值
对 scrollLeft
赋值等价于触发 scrollTo
,所有导致滚动产生的行为都会触发 reflow,笔者查了一些材料,目前次要揣测是滚动条呈现会导致可视区域变窄,所以须要 reflow。
focus()
elem.focus()
(源码)
能够依据源码看一下正文,次要是这一段:
// Ensure we have clean style (including forced display locks).
GetDocument().UpdateStyleAndLayoutTreeForNode(this)
即在聚焦元素时,尽管没有拿元素地位信息的诉求,但指不定要被聚焦的元素被暗藏或者移除了,此时必须调用 UpdateStyleAndLayoutTreeForNode
重排重绘函数,确保元素状态更新后能力持续操作。
还有一些其余 element API:
elem.computedRole
,elem.computedName
elem.innerText
(源码)
innerText
也须要重排后能力拿到正确内容。
获取 window 信息
window.scrollX
,window.scrollY
window.innerHeight
,window.innerWidth
window.visualViewport.height
/width
/offsetTop
/offsetLeft
(源码)
和元素级别一样,为了拿到正确宽高和地位信息,必须重排。
document 相干
document.scrollingElement
仅重绘document.elementFromPoint
elementFromPoint
因为要拿到准确地位的元素,必须重排。
Form 相干
inputElem.focus()
inputElem.select()
,textareaElem.select()
focus
、select
触发重排的起因和 elem.focus
相似。
鼠标事件相干
mouseEvt.layerX
,mouseEvt.layerY
,mouseEvt.offsetX
,mouseEvt.offsetY
(源码)
鼠标相干地位计算,必须依赖一个正确的排布,所以必须触发 reflow。
getComputedStyle
getComputedStyle
通常会导致重排和重绘,是否触发重排取决于是否拜访了地位相干的 key 等因素。
Range 相干
range.getClientRects()
,range.getBoundingClientRect()
获取选中区域的大小,必须 reflow 能力保障精确性。
SVG
大量 SVG 办法会引发重排,就不一一枚举了,总之应用 SVG 操作时也要像操作 dom 一样审慎。
contenteditable
被设置为 contenteditable
的元素内,包含将图像复制到剪贴板在内,大量操作都会导致重排。(源码)
精读
What forces layout / reflow 上面援用了几篇对于 reflow 的相干文章,笔者挑几个重要的总结一下。
repaint-reflow-restyle
repaint-reflow-restyle 提到古代浏览器会将屡次 dom 操作合并,但像 IE 等其余内核浏览器就不保障有这样的实现了,因而给出了一个平安写法:
// bad
var left = 10,
top = 10;
el.style.left = left + "px";
el.style.top = top + "px";
// better
el.className += "theclassname";
// or when top and left are calculated dynamically...
// better
el.style.cssText += "; left:" + left + "px; top:" + top + "px;";
比方用一次 className 的批改,或一次 cssText
的批改保障浏览器肯定触发一次重排。但这样可维护性会升高很多,不太举荐。
avoid large complex layouts
avoid large complex layouts 重点强调了读写拆散,首先看上面的 bad case:
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';
}
}
在 for 循环中一直拜访元素宽度,并批改其宽度,会导致浏览器执行 N 次 reflow。
尽管当 JavaScript 运行时,前一帧中的所有旧布局值都是已知的,但当你对布局做了批改后,前一帧所有布局值缓存都会作废,因而当下次获取值时,不得不从新触发一次 reflow。
而读写拆散的话,就代表了集中读,尽管读的次数还是那么多,但从第二次开始就能够从布局缓存中拿数据,不必触发 reflow 了。
另外还提到 flex 布局比传统 float 重排速度快很多(3ms vs 16ms),所以能用 flex 做的布局就尽量不要用 float 做。
really fixing layout thrashing
really fixing layout thrashing 提到了用 fastdom 实际读写拆散:
ids.forEach(id => {fastdom.measure(() => {const top = elements[id].offsetTop
fastdom.mutate(() => {elements[id].setLeft(top)
})
})
})
fastdom
是一个能够在不拆散代码的状况下,拆散读写执行的库,尤其适宜用在 reflow 性能优化场景。每一个 measure
、mutate
都会推入执行队列,并在 window.requestAnimationFrame 机会执行。
总结
回流无奈防止,但须要管制在失常频率范畴内。
咱们须要学习拜访哪些属性或办法会导致回流,能不应用就不要用,尽量做到读写拆散。在定义要频繁触发回流的元素时,尽量使其脱离文档流,缩小回流产生的影响。
探讨地址是:精读《web reflow》· Issue #420 · dt-fe/weekly
如果你想参加探讨,请 点击这里,每周都有新的主题,周末或周一公布。前端精读 – 帮你筛选靠谱的内容。
关注 前端精读微信公众号
<img width=200 src=”https://img.alicdn.com/tfs/TB165W0MCzqK1RjSZFLXXcn2XXa-258-258.jpg”>
版权申明:自在转载 - 非商用 - 非衍生 - 放弃署名(创意共享 3.0 许可证)