热门标签 | 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;



推荐阅读
  • Flutter 核心技术与混合开发模式深入解析
    本文深入探讨了 Flutter 的核心技术,特别是其混合开发模式,包括统一管理模式和三端分离模式,以及混合栈原理。通过对比不同模式的优缺点,帮助开发者选择最适合项目的混合开发策略。 ... [详细]
  • 理解浏览器历史记录(2)hashchange、pushState
    阅读目录1.hashchange2.pushState本文也是一篇基础文章。继上文之后,本打算去研究pushState,偶然在一些信息中发现了锚点变 ... [详细]
  • 问题场景用Java进行web开发过程当中,当遇到很多很多个字段的实体时,最苦恼的莫过于编辑字段的查看和修改界面,发现2个页面存在很多重复信息,能不能写一遍?有没有轮子用都不如自己造。解决方式笔者根据自 ... [详细]
  • CentOS7通过RealVNC实现多人使用服务器桌面
    背景:公司研发团队通过VNC登录到CentOS服务器的桌面实现软件开发工作为防止数据外泄,需要在RealVNC设置禁止传输文件、访问粘贴板等策略过程&# ... [详细]
  • 在将 Android Studio 从 3.0 升级到 3.1 版本后,遇到项目无法正常编译的问题,具体错误信息为:org.gradle.api.tasks.TaskExecutionException: Execution failed for task ':app:processDemoProductDebugResources'。 ... [详细]
  • 实践指南:使用Express、Create React App与MongoDB搭建React开发环境
    本文详细介绍了如何利用Express、Create React App和MongoDB构建一个高效的React应用开发环境,旨在为开发者提供一套完整的解决方案,包括环境搭建、数据模拟及前后端交互。 ... [详细]
  • VC++ 文件操作实践案例
    本文介绍如何在Visual C++ 6.0环境下,通过下载的VC源代码实现文件的基本读写操作,并解决常见的运行错误。 ... [详细]
  • 使用Echarts for Weixin 小程序实现中国地图及区域点击事件
    本文介绍了如何使用Echarts for Weixin在微信小程序中构建中国地图,并实现区域点击事件。包括效果展示、条件准备和逻辑实现的具体步骤。 ... [详细]
  • 本文探讨了如何在游戏启动画面中移除广告,特别是在游戏数据加载期间(大约5-6秒)广告会短暂显示的问题。通过调整XML布局和代码逻辑,可以实现广告的延迟加载或完全移除。 ... [详细]
  • android开发分享荐                                                         Android思维导图布局:效果展示及使用方法
    思维导图布局的前身是树形布局,对树形布局基本使用还不太了解的朋友可以先看看我写的树形布局系列教程,了解了树形布局的使用方法后再来阅读本文章。先睹为快来看看效果吧,横向效果如下:纵向 ... [详细]
  • 本文详细介绍了如何在Android应用中实现重复报警功能。示例代码可在以下路径找到:https://developer.android.com/samples/RepeatingAlarm/index.html。首先,我们将从Manifest文件开始分析。 ... [详细]
  • Vulnhub DC3 实战记录与分析
    本文记录了在 Vulnhub DC3 靶机上的渗透测试过程,包括漏洞利用、内核提权等关键步骤,并总结了实战经验和教训。 ... [详细]
  • 深入解析RelativeLayout、LinearLayout与FrameLayout的性能差异
    本文详细分析了FrameLayout和LinearLayout的性能对比,通过具体的测量数据和源码解析,探讨了不同布局在不同场景下的性能表现。 ... [详细]
  • 本文介绍了一种通过设置主题(Theme)来实现快速启动的Android引导页,并详细说明了如何避免因不同屏幕分辨率导致的图片拉伸问题。 ... [详细]
  • SwipeRefreshLayout 是一个常用的刷新控件,可以包裹一个可滑动的子控件(如 ListView 或 RecyclerView)以实现竖直滑动时的页面刷新。然而,它本身并不支持上拉加载更多。本文将介绍如何通过继承 SwipeRefreshLayout 来实现这一功能。 ... [详细]
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社区 版权所有