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



推荐阅读
  • 如何在HTML中获取鼠标的当前位置
    本文介绍了在HTML中获取鼠标当前位置的三种方法,分别是相对于屏幕的位置、相对于窗口的位置以及考虑了页面滚动因素的位置。通过这些方法可以准确获取鼠标的坐标信息。 ... [详细]
  • 移动端常用单位——rem的使用方法和注意事项
    本文介绍了移动端常用的单位rem的使用方法和注意事项,包括px、%、em、vw、vh等其他常用单位的比较。同时还介绍了如何通过JS获取视口宽度并动态调整rem的值,以适应不同设备的屏幕大小。此外,还提到了rem目前在移动端的主流地位。 ... [详细]
  • 本文介绍了OC学习笔记中的@property和@synthesize,包括属性的定义和合成的使用方法。通过示例代码详细讲解了@property和@synthesize的作用和用法。 ... [详细]
  • http:my.oschina.netleejun2005blog136820刚看到群里又有同学在说HTTP协议下的Get请求参数长度是有大小限制的,最大不能超过XX ... [详细]
  • 本文介绍了通过ABAP开发往外网发邮件的需求,并提供了配置和代码整理的资料。其中包括了配置SAP邮件服务器的步骤和ABAP写发送邮件代码的过程。通过RZ10配置参数和icm/server_port_1的设定,可以实现向Sap User和外部邮件发送邮件的功能。希望对需要的开发人员有帮助。摘要长度:184字。 ... [详细]
  • 自动轮播,反转播放的ViewPagerAdapter的使用方法和效果展示
    本文介绍了如何使用自动轮播、反转播放的ViewPagerAdapter,并展示了其效果。该ViewPagerAdapter支持无限循环、触摸暂停、切换缩放等功能。同时提供了使用GIF.gif的示例和github地址。通过LoopFragmentPagerAdapter类的getActualCount、getActualItem和getActualPagerTitle方法可以实现自定义的循环效果和标题展示。 ... [详细]
  • ASP.NET2.0数据教程之十四:使用FormView的模板
    本文介绍了在ASP.NET 2.0中使用FormView控件来实现自定义的显示外观,与GridView和DetailsView不同,FormView使用模板来呈现,可以实现不规则的外观呈现。同时还介绍了TemplateField的用法和FormView与DetailsView的区别。 ... [详细]
  • 本文介绍了在Linux下安装和配置Kafka的方法,包括安装JDK、下载和解压Kafka、配置Kafka的参数,以及配置Kafka的日志目录、服务器IP和日志存放路径等。同时还提供了单机配置部署的方法和zookeeper地址和端口的配置。通过实操成功的案例,帮助读者快速完成Kafka的安装和配置。 ... [详细]
  • 本文介绍了腾讯最近开源的BERT推理模型TurboTransformers,该模型在推理速度上比PyTorch快1~4倍。TurboTransformers采用了分层设计的思想,通过简化问题和加速开发,实现了快速推理能力。同时,文章还探讨了PyTorch在中间层延迟和深度神经网络中存在的问题,并提出了合并计算的解决方案。 ... [详细]
  • 解决nginx启动报错epoll_wait() reported that client prematurely closed connection的方法
    本文介绍了解决nginx启动报错epoll_wait() reported that client prematurely closed connection的方法,包括检查location配置是否正确、pass_proxy是否需要加“/”等。同时,还介绍了修改nginx的error.log日志级别为debug,以便查看详细日志信息。 ... [详细]
  • CEPH LIO iSCSI Gateway及其使用参考文档
    本文介绍了CEPH LIO iSCSI Gateway以及使用该网关的参考文档,包括Ceph Block Device、CEPH ISCSI GATEWAY、USING AN ISCSI GATEWAY等。同时提供了多个参考链接,详细介绍了CEPH LIO iSCSI Gateway的配置和使用方法。 ... [详细]
  • Learning to Paint with Model-based Deep Reinforcement Learning
    本文介绍了一种基于模型的深度强化学习方法,通过结合神经渲染器,教机器像人类画家一样进行绘画。该方法能够生成笔画的坐标点、半径、透明度、颜色值等,以生成类似于给定目标图像的绘画。文章还讨论了该方法面临的挑战,包括绘制纹理丰富的图像等。通过对比实验的结果,作者证明了基于模型的深度强化学习方法相对于基于模型的DDPG和模型无关的DDPG方法的优势。该研究对于深度强化学习在绘画领域的应用具有重要意义。 ... [详细]
  • 本文详细介绍了Android中的坐标系以及与View相关的方法。首先介绍了Android坐标系和视图坐标系的概念,并通过图示进行了解释。接着提到了View的大小可以超过手机屏幕,并且只有在手机屏幕内才能看到。最后,作者表示将在后续文章中继续探讨与View相关的内容。 ... [详细]
  • 本文介绍了Cocos2dx学习笔记中的更新函数scheduleUpdate、进度计时器CCProgressTo和滚动视图CCScrollView的用法。详细介绍了scheduleUpdate函数的作用和使用方法,以及schedule函数的区别。同时,还提供了相关的代码示例。 ... [详细]
  • OpenStackQ版本已经发布了一段时间了。今天,小编来总结一下OpenStackQ版本核心组件的各项主要新功能,再来汇总一下最近2年来OpenStackN、O、P、Q各版本核心 ... [详细]
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社区 版权所有