乐趣区

关于前端:精读web-reflow

网页重排(回流)是妨碍流畅性的重要起因之一,联合 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()

focusselect 触发重排的起因和 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 性能优化场景。每一个 measuremutate 都会推入执行队列,并在 window.requestAnimationFrame 机会执行。

总结

回流无奈防止,但须要管制在失常频率范畴内。

咱们须要学习拜访哪些属性或办法会导致回流,能不应用就不要用,尽量做到读写拆散。在定义要频繁触发回流的元素时,尽量使其脱离文档流,缩小回流产生的影响。

探讨地址是:精读《web reflow》· Issue #420 · dt-fe/weekly

如果你想参加探讨,请 点击这里,每周都有新的主题,周末或周一公布。前端精读 – 帮你筛选靠谱的内容。

关注 前端精读微信公众号

<img width=200 src=”https://img.alicdn.com/tfs/TB165W0MCzqK1RjSZFLXXcn2XXa-258-258.jpg”>

版权申明:自在转载 - 非商用 - 非衍生 - 放弃署名(创意共享 3.0 许可证)

退出移动版