浏览器的回流和重绘

    在前端开发中,尤其是在处理网页的与渲染时,“回流”(Reflow)和“重”(Repaint)是两个至关重要的概念。这两个过程在浏览器渲染页面时发挥着重要的作用,但它们有着不同的触发条件和性能影响。下面将详细解释这两个概念及其差异。

    回流重绘对于lighthouse评分的影响,可以参考我们这篇文章《优化性能:回流与重绘对Lighthouse指标的影响》

    回流(Reflow)

    回流是指浏览器重新计算元素的几何属性(如宽度、高度、位置等)的过程。当页面的布局发生变化时,例如:

    • 改变元素的尺寸(如通过修改widthheight)。
    • 改变元素的位置(如通过修改marginpadding)。
    • 添加或删除DOM元素。
    • 修改元素的内容或样式(如更改字体大小)。

    一旦这些变化发生,浏览器需要重新计算页面的布局,这就是回流。在回流过程中,浏览器会通过中心化模型重新计算所有相关元素的位置和大小,确保页面的布局能够反映这些更改。

    回流是一个成本较高的操作,特别是在页面包含大量DOM元素时,频繁的回流会导致性能下降,影响用户体验。

    重绘(Repaint)

    重绘是指浏览器重新绘制元素的过程,通常是因为元素的样式发生了变化,但不会影响其几何属性。重绘的典型情况包括:

    • 修改元素的颜色(如colorbackground-color)。
    • 修改阴影、边框等视觉效果。

    与回流不同,重绘不会影响其他元素的布局,因此其开销相对较小。不过,如果一个元素发生了重绘,而该元素又影响到多个子元素的视觉表现,可能会导致很多元素都需要被重绘,从而影响性能。

    回流与重绘的关系

    回流和重绘是相互关联的过程,当发生回流时,通常也会引发重绘。具体来说,如果一个元素的尺寸或位置发生变化,浏览器首先会进行回流来更新布局,然后再执行重绘来更新元素的视觉效果。

    但反之则不成立,重绘不会导致回流。优化网页性能时,开发者应该尽量减少回流的次数,避免在JavaScript中频繁操作DOM,特别是影响布局的操作。以下是一些优化回流的技巧:

    • 将DOM变化集中到一起,尽量减少对DOM的多次操作。
    • 使用display: none隐藏元素,使其不参与布局计算。
    • 使用documentFragment等方法批量操作DOM。

    如何避免回流和重绘

    为了优化网页的性能和响应速度,开发者应该尽量减少回流和重绘的次数。接下来,我们将探讨一些实用的方法和策略,帮助你有效避免回流和重绘。

    1. 批量处理DOM操作

    • 集中更新:尽量将多个DOM操作集中在一起进行,而不是分散在不同的操作中。例如,如果要对多个元素应用样式变化,应该将其合并为一次操作,而不是逐一修改。

    • 使用文档片段:可以使用document.createDocumentFragment()创建一个文档片段,将多个DOM节点添加到这个片段中,最后一次性将片段插入到实际的DOM中,这样可以减少回流和重绘的次数。

    2. 减少复杂的布局计算

    • 避免使用CSS属性触发回流:如offsetWidthoffsetHeightgetComputedStyle()等避免频繁调用,特别是在动画或循环中,因为它们会强制浏览器计算布局。

    • 使用绝对定位:对于不需要参与文档流的元素,可以使用position: absoluteposition: fixed,可以减少回流的发生,因为它们不会影响其他元素。

    3. 用CSS进行动画效果

    • 利用CSS过渡和动画:CSS过渡(transition)和动画(animation)通常在GPU中运行,并且不涉及回流。这使得它们比通过JavaScript实现的动画更加高效。尽量使用transformopacity等属性,这些属性的变更通常不会引发回流。

    4. 避免频繁的样式计算

    • 缓存计算结果:如果需要多次使用某个计算结果,存储在变量中并使用它,而不是每次都重新计算。例如,获取某个元素的高度时,第一次计算后,将结果缓存,后续操作直接使用cached值。

    • 使用CSS类进行样式变化:通过添加或移除CSS类,而不是直接修改元素样式,可以减少JavaScript对DOM的直接操作。例如,使用classList来切换类名。

    5. 结构优化

    • 合理的DOM结构:保持DOM结构的扁平化可以减少回流的复杂性,避免嵌套过深的元素,确保元素之间的关系简明清晰。

    • 懒加载:对于不需要立即展示的元素,可以使用懒加载(Lazy Loading)技术,延迟元素的加载,减少初始渲染所需的回流和重绘。

    6. 使用虚拟DOM

    • 框架优化:现代框架(如React、Vue等)使用虚拟DOM来优化渲染性能。开发者可以利用这些框架,减少直接对真实DOM的操作,从而降低回流和重绘的频率。

    7. 监测和分析性能

    • 使用浏览器开发者工具:浏览器内置的开发者工具可以帮助开发者监测回流和重绘的发生。通过使用性能分析功能,可以识别引起性能问题的代码,从而进行优化。

    资源加载(图片/视频等)对回流和重绘的影响

    影响回流的方式

    • 占位计算:当网页中的图片或视频资源未加载完成时,浏览器无法确定它们的最终尺寸。如果不为这些资源设置明确的宽度和高度,浏览器在加载完成后可能会触发回流,以重新计算页面布局,因为它需要重新安排其他元素的位置。
    • 插入新元素:当图片或视频被动态加载到页面中(例如,通过JavaScript动态添加),这会导致浏览器需要重新计算布局,从而引发回流。

    影响重绘的方式

    • 样式变化:当图片或视频加载完成并显示出来时,可能会引发重绘,特别是当其样式(如透明度、滤镜等)发生变化时。浏览器需要重新绘制相应的元素及其相关的子元素。

    如何处理资源加载对回流和重绘的影响

    为减少资源加载期间对回流和重绘的影响,可以采取以下策略:

    1. 预设尺寸

    • 设置宽度和高度:在HTML中为图片和视频元素设置widthheight属性,或者通过CSS明确设置它们的尺寸。这样可以确保浏览器在渲染时保留空间,避免在资源加载后触发回流。
    <img src="image.jpg" width="600" height="400" alt="示例图片">
    

    2. 使用占位符

    • 占位符图片:在实际加载的图片或视频之前,先展示一个占位符(例如,低分辨率图像或加载动画),从而减少回流的频率。

    3. 懒加载

    • 懒加载技术:对于页面不在可视区域的图片或视频,可以使用懒加载,延迟加载这些资源,直到用户滚动到相应的位置。这减少了初始加载时的回流和重绘。
    <img src="placeholder.jpg" data-src="image.jpg" class="lazyload" alt="示例图片">
    

    使用JavaScript或其他库(如Intersection Observer API)在用户滚动到图片位置时动态加载真实的图片。

    4. 按需加载和异步加载

    • 按需加载:对于内容较多的页面,可以按需加载(如分页加载),仅在用户需要时加载特定资源,减少初始负担和回流。

    • 异步加载:对于JavaScript文件可以使用asyncdefer属性,使其异步执行,避免阻塞渲染,从而减少回流的发生。

    5. 优化CSS和JavaScript

    • 减少重绘触发:在进行DOM操作时,尽量批量修改并使用类替换的方法,避免逐个设置元素的样式,降低重绘的数量。

    • 使用transform而不是top/left:在应用动画时,使用CSS的transform属性(如translate)而不是直接修改topleft,因为transform不会触发回流。