性能优化之重绘与回流

什么是重绘与回流?

回流(Reflow)(重排) 是浏览器为了重新渲染部分或全部文档而重新计算元素的几何属性的过程。这包括了元素的尺寸、位置、布局和页面的结构。回流是一种比较昂贵的操作,因为它会触发浏览器重新计算并重新绘制元素,可能导致性能下降。

重绘(Repaint) 是当元素样式发生改变,但没有影响其布局时,浏览器只需要重新绘制元素的外观的过程。这不涉及元素的位置或尺寸的改变。相比于回流,重绘是一种较为轻量级的操作。

触发回流的情况?

  1. 改变窗口大小。
  2. 改变元素的尺寸、位置、边距、填充等。
  3. 内容发生变化,比如文本变化或图片被另一个不同尺寸的图片所替代。
  4. 添加或删除可见的DOM元素。
  5. 页面一开始渲染的时候(这肯定避免不了)。
  6. 查询某些属性或调用某些方法,需要布局信息,如 offsetTop、offsetLeft、offsetWidth、offsetHeight、scrollTop、scrollLeft、scrollWidth、scrollHeight、 clientTop、clientLeft、clientWidth、clientHeight、 getComputedStyle()、getBoundingClientRect。 等。

注意:回流一定会触发重绘,而重绘不一定会回流

触发重绘的情况?

  1. 修改元素的背景色、文字颜色、边框颜色等。
  2. 改变元素的可见性,例如使用 visibility: hidden
  3. 使用CSS3的某些属性,如 transformopacity,但不影响布局

减少回流与重绘

最小化重绘和重排

由于重绘和重排可能代价比较昂贵,因此最好就是可以减少它的发生次数。为了减少发生次数,我们可以合并多次对DOM和样式的修改,然后一次处理掉。考虑这个例子

js 复制代码
const el = document.getElementById('test');
el.style.padding = '5px';
el.style.borderLeft = '1px';
el.style.borderRight = '2px';

例子中,有三个样式属性被修改了,每一个都会影响元素的几何结构,引起回流。当然,大部分现代浏览器都对其做了优化,因此,只会触发一次重排。但是如果在旧版的浏览器或者在上面代码执行的时候,有其他代码访问了布局信息(上文中的会触发回流的布局信息),那么就会导致三次重排。

因此,我们可以合并所有的改变然后依次处理,比如我们可以采取以下的方式:

js 复制代码
const el = document.getElementById('test');
el.style.cssText += 'border-left: 1px; border-right: 2px; padding: 5px;';

使用css硬件加速

比起考虑如何减少回流重绘,我们更期望的是,根本不要回流重绘。这个时候,css3硬件加速就闪亮登场啦!!

某些浏览器可以通过启用CSS硬件加速来减少回流和重绘的开销,具体方式是使用transformopacity属性,并设置will-change来提示浏览器进行硬件加速。

css 复制代码
.element {
  transform: translateZ(0); /* 启用硬件加速 */
  will-change: transform; /* 提示浏览器要加速的属性 */
}

划重点:

1. 使用css3硬件加速,可以让transform、opacity、filters这些动画不会引起回流重绘 。

2. 对于动画的其它属性,比如background-color这些,还是会引起回流重绘的,不过它还是可以提升这些动画的性能。

如何使用

常见的触发硬件加速的css属性:

  • transform
  • opacity
  • filters
  • Will-change

will-change

will-change属性可以提示浏览器哪些属性将会被改变,以便进行优化。在使用动画或者即将进行的重绘操作前,你可以使用will-change来通知浏览器,如下所示

css 复制代码
.element {
  will-change: transform, opacity;
}

// 在动画之前添加上述代码,以启用硬件加速

避免频繁操作布局属性

当我们访问元素的一些属性的时候,会导致浏览器强制清空队列,进行强制同步布局。举个例子,比如说我们想将一个p标签数组的宽度赋值为一个元素的宽度,我们可能写出这样的代码:

ini 复制代码
function initP() {
    for (let i = 0; i < paragraphs.length; i++) {
        paragraphs[i].style.width = box.offsetWidth + 'px';
    }
}

这段代码看上去是没有什么问题,可是其实会造成很大的性能问题。在每次循环的时候,都读取了box的一个offsetWidth属性值,然后利用它来更新p标签的width属性。这就导致了每一次循环的时候,浏览器都必须先使上一次循环中的样式更新操作生效,才能响应本次循环的样式读取操作。每一次循环都会强制浏览器刷新队列。我们可以优化为:

js 复制代码
const width = box.offsetWidth;
function initP() {
    for (let i = 0; i < paragraphs.length; i++) {
        paragraphs[i].style.width = width + 'px';
    }
}
相关推荐
WebDeveloper20011 小时前
如何使用美国域名中心US Domain Center和WordPress创建商业网站
运维·服务器·css·网络·html
真的很上进4 小时前
如何借助 Babel+TS+ESLint 构建现代 JS 工程环境?
java·前端·javascript·css·react.js·vue·html
sanguine__8 小时前
APIs-day2
javascript·css·css3
LUwantAC9 小时前
CSS(一):选择器
前端·css
Simaoya10 小时前
【vue】圆环呼吸灯闪烁效果(模拟扭蛋机出口处灯光)
javascript·css·vue.js
LOVE️YOU11 小时前
HTML&CSS&JavaScript&DOM 之间的关系?
前端·javascript·css·html
NoneCoder12 小时前
CSS系列(26)-- 动画性能优化详解
前端·css·性能优化
萧寂17312 小时前
ios底部小横条高度适配
css
机器视觉李小白16 小时前
使用 HTML 和 CSS 实现绚丽的节日烟花效果
css·html·烟花·节日·节日祝福