热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

精读《webreflow》

网页重排(回流)是阻碍流畅性的重要原因之一,结合Whatforceslayoutreflow这篇文章与引用,整理一下回流的起

网页重排(回流)是阻碍流畅性的重要原因之一,结合 What forces layout / reflow 这篇文章与引用,整理一下回流的起因与优化思考。

借用这张经典图:

db185cf3c3dbfb6045f5a64912986a72.png

网页渲染会经历 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 &#61; 0; i < paragraphs.length; i&#43;&#43;) {paragraphs[i].style.width &#61; box.offsetWidth &#43; &#39;px&#39;;}
}

在 for 循环中不断访问元素宽度&#xff0c;并修改其宽度&#xff0c;会导致浏览器执行 N 次 reflow。

虽然当 Javascript 运行时&#xff0c;前一帧中的所有旧布局值都是已知的&#xff0c;但当你对布局做了修改后&#xff0c;前一帧所有布局值缓存都会作废&#xff0c;因此当下次获取值时&#xff0c;不得不重新触发一次 reflow。

而读写分离的话&#xff0c;就代表了集中读&#xff0c;虽然读的次数还是那么多&#xff0c;但从第二次开始就可以从布局缓存中拿数据&#xff0c;不用触发 reflow 了。

另外还提到 flex 布局比传统 float 重排速度快很多&#xff08;3ms vs 16ms&#xff09;&#xff0c;所以能用 flex 做的布局就尽量不要用 float 做。

really fixing layout thrashing

really fixing layout thrashing 提到了用 fastdom 实践读写分离&#xff1a;

ids.forEach(id &#61;> {fastdom.measure(() &#61;> {const top &#61; elements[id].offsetTopfastdom.mutate(() &#61;> {elements[id].setLeft(top)})})
})

fastdom 是一个可以在不分离代码的情况下&#xff0c;分离读写执行的库&#xff0c;尤其适合用在 reflow 性能优化场景。每一个 measuremutate 都会推入执行队列&#xff0c;并在 window.requestAnimationFrame 时机执行。

总结

回流无法避免&#xff0c;但需要控制在正常频率范围内。

我们需要学习访问哪些属性或方法会导致回流&#xff0c;能不使用就不要用&#xff0c;尽量做到读写分离。在定义要频繁触发回流的元素时&#xff0c;尽量使其脱离文档流&#xff0c;减少回流产生的影响。

讨论地址是&#xff1a;精读《web reflow》· Issue #420 · dt-fe/weekly

如果你想参与讨论&#xff0c;请 点击这里&#xff0c;每周都有新的主题&#xff0c;周末或周一发布。前端精读 - 帮你筛选靠谱的内容。

版权声明&#xff1a;自由转载-非商用-非衍生-保持署名&#xff08;创意共享 3.0 许可证&#xff09;



推荐阅读
  • 深入解析 Android 中 EditText 的 getLayoutParams 方法及其代码应用实例 ... [详细]
  • 在处理 XML 数据时,如果需要解析 `` 标签的内容,可以采用 Pull 解析方法。Pull 解析是一种高效的 XML 解析方式,适用于流式数据处理。具体实现中,可以通过 Java 的 `XmlPullParser` 或其他类似的库来逐步读取和解析 XML 文档中的 `` 元素。这样不仅能够提高解析效率,还能减少内存占用。本文将详细介绍如何使用 Pull 解析方法来提取 `` 标签的内容,并提供一个示例代码,帮助开发者快速解决问题。 ... [详细]
  • 【问题】在Android开发中,当为EditText添加TextWatcher并实现onTextChanged方法时,会遇到一个问题:即使只对EditText进行一次修改(例如使用删除键删除一个字符),该方法也会被频繁触发。这不仅影响性能,还可能导致逻辑错误。本文将探讨这一问题的原因,并提供有效的解决方案,包括使用Handler或计时器来限制方法的调用频率,以及通过自定义TextWatcher来优化事件处理,从而提高应用的稳定性和用户体验。 ... [详细]
  • Android中将独立SO库封装进JAR包并实现SO库的加载与调用
    在Android开发中,将独立的SO库封装进JAR包并实现其加载与调用是一个常见的需求。本文详细介绍了如何将SO库嵌入到JAR包中,并确保在外部应用调用该JAR包时能够正确加载和使用这些SO库。通过这种方式,开发者可以更方便地管理和分发包含原生代码的库文件,提高开发效率和代码复用性。文章还探讨了常见的问题及其解决方案,帮助开发者避免在实际应用中遇到的坑。 ... [详细]
  • 自然语言处理(NLP)——LDA模型:对电商购物评论进行情感分析
    目录一、2020数学建模美赛C题简介需求评价内容提供数据二、解题思路三、LDA简介四、代码实现1.数据预处理1.1剔除无用信息1.1.1剔除掉不需要的列1.1.2找出无效评论并剔除 ... [详细]
  • Android 自定义 RecycleView 左滑上下分层示例代码
    为了满足项目需求,需要在多个场景中实现左滑删除功能,并且后续可能在列表项中增加其他功能。虽然网络上有很多左滑删除的示例,但大多数封装不够完善。因此,我们尝试自己封装一个更加灵活和通用的解决方案。 ... [详细]
  • 如果应用程序经常播放密集、急促而又短暂的音效(如游戏音效)那么使用MediaPlayer显得有些不太适合了。因为MediaPlayer存在如下缺点:1)延时时间较长,且资源占用率高 ... [详细]
  • IOS Run loop详解
    为什么80%的码农都做不了架构师?转自http:blog.csdn.netztp800201articledetails9240913感谢作者分享Objecti ... [详细]
  • 本文介绍如何在 Android 中自定义加载对话框 CustomProgressDialog,包括自定义 View 类和 XML 布局文件的详细步骤。 ... [详细]
  • 实验九:使用SharedPreferences存储简单数据
    本实验旨在帮助学生理解和掌握使用SharedPreferences存储和读取简单数据的方法,包括程序参数和用户选项。 ... [详细]
  • 本文介绍了如何利用 `matplotlib` 库中的 `FuncAnimation` 类将 Python 中的动态图像保存为视频文件。通过详细解释 `FuncAnimation` 类的参数和方法,文章提供了多种实用技巧,帮助用户高效地生成高质量的动态图像视频。此外,还探讨了不同视频编码器的选择及其对输出文件质量的影响,为读者提供了全面的技术指导。 ... [详细]
  • 大类|电阻器_使用Requests、Etree、BeautifulSoup、Pandas和Path库进行数据抓取与处理 | 将指定区域内容保存为HTML和Excel格式
    大类|电阻器_使用Requests、Etree、BeautifulSoup、Pandas和Path库进行数据抓取与处理 | 将指定区域内容保存为HTML和Excel格式 ... [详细]
  • MATLAB字典学习工具箱SPAMS:稀疏与字典学习的详细介绍、配置及应用实例
    SPAMS(Sparse Modeling Software)是一个强大的开源优化工具箱,专为解决多种稀疏估计问题而设计。该工具箱基于MATLAB,提供了丰富的算法和函数,适用于字典学习、信号处理和机器学习等领域。本文将详细介绍SPAMS的配置方法、核心功能及其在实际应用中的典型案例,帮助用户更好地理解和使用这一工具箱。 ... [详细]
  • 2018 HDU 多校联合第五场 G题:Glad You Game(线段树优化解法)
    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=6356在《Glad You Game》中,Steve 面临一个复杂的区间操作问题。该题可以通过线段树进行高效优化。具体来说,线段树能够快速处理区间更新和查询操作,从而大大提高了算法的效率。本文详细介绍了线段树的构建和维护方法,并给出了具体的代码实现,帮助读者更好地理解和应用这一数据结构。 ... [详细]
  • 设计实战 | 10个Kotlin项目深度解析:首页模块开发详解
    设计实战 | 10个Kotlin项目深度解析:首页模块开发详解 ... [详细]
author-avatar
邹balitas_611
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有