什么是重排?如何减少重排?

重排或回流、重绘

需求: 为了让项目的运行效率更高,减少开销,需要对重排、回流、重绘有一定的了解。 重排和回流其实是一回事。

重排

  • 涵义:重排(Reflow)是当DOM(文档对象模型)的变化影响了元素的几何信息(如元素的位置和尺寸大小)时,浏览器需要重新计算元素的几何属性,并将其安放在界面中的正确位置的过程。这个过程也被称为回流。重排基本上是对页面布局进行重新生成和元素重新排列。

  • 以下情况可能会引发重排:

shell 复制代码
页面初始渲染。
添加或删除可见的DOM元素。
元素位置的改变,或者使用动画。
改变元素尺寸,比如边距、填充、边框、宽度和高度等。
填充内容的改变,比如文本的改变或图片大小改变而引起的计算值宽度和高度的改变。
浏览器窗口尺寸的变化(resize事件发生时)。
设置style属性的值,因为通过设置style属性改变节点样式的话,每一次设置都会触发一次reflow。
  • 要减少重排,可以尝试以下策略:
    1. 减少直接操作DOM元素:尽量使用className来控制样式,而不是直接修改style属性。
    2. 避免使用table:因为table属性的变化可能会导致布局重排或重绘。
    3. 使用CSS3动画代替JavaScript动画:因为CSS3动画不会触发重排。
    4. 避免在循环中操作DOM:把DOM节点的属性值放在一个循环里当成循环里的变量可能会引发重排。
    5. 使用DocumentFragment:如果需要创建多个DOM节点,可以先使用DocumentFragment创建完,然后一次性地加入document,这样可以减少重排的次数。
    6. 使用display: none隐藏元素:当对父盒子进行操作前,先使用display: none隐藏它,等所有的操作完成后,再将这个父盒子显示出来。这样,中间的操作都不会造成重排。

回流

回流:英文叫reflow,指的是当渲染树中的节点信息发生了大小、边距等问题,需要重新计算各节点和css具体的大小和位置。

例:在css中对一个div修饰的样式中,使用了宽度50%,此时需要将50%转换为具体的像素,这个计算的过程,就是回流的过程。

容易造成回流的操作:

  1. 布局流相关操作

    • 盒模型的相关操作会触发重新布局
    • 定位相关操作会触发重新布局
    • 浮动相关操作会触发重新布局
  2. 改变节点内的内容

    改变节点的结构或其中的文本结构会触发重新布局

css 复制代码
-   width
-   height
-   padding
-   border
-   margin
-   position
-   top
-   left
-   bottom
-   right
-   float
-   clear
-   text-align
-   vertical-align
-   line-height
-   font-weight
-   font-size
-   font-family
-   overflow
-   white-space

重绘

重绘:英文叫repaint,当节点的部分属性发生变化,但不影响布局,只需要重新计算节点在屏幕中的绝对位置并渲染的过程,就叫重绘。比如:改变元素的背景颜色、字体颜色等操作会造成重绘。

回流的过程在重绘的过程前面,所以回流一定会重绘,但重绘不一定会引起回流。

容易造成重绘操作的css:

css 复制代码
-   color
-   border-style
-   border-radius
-   text-decoration
-   box-shadow
-   outline
-   background

每次回流都会对浏览器造成额外的计算消耗,所以浏览器对于回流和重绘有一定的优化机制。浏览器通常都会将多次回流操作放入一个队列中,等过了一段时间或操作达到了一定的临界值,然后才会挨个执行,这样能节省一些计算消耗。但是在获取布局信息操作的时候,会强制将队列清空,也就是强制回流,比如访问或操作以下或方法时:

css 复制代码
-   offsetTop
-   offsetLeft
-   offsetWidth
-   offsetHeight
-   scrollTop
-   scrollLeft
-   scrollWidth
-   scrollHeight
-   clientTop
-   clientLeft
-   clientWidth
-   clientHeight
-   getComputedStyle()

这些属性或方法都需要得到最新的布局信息,所以浏览器必须去回流执行。因此,在项目中,尽量避免使用上述属性或方法,如果非要使用的时候,也尽量将值缓存起来,而不是一直获取。

减少回流和重绘

合并样式修改

减少造成回流的次数,如果要给一个节点操作多个css属性,而每一个都会造成回流的话,尽量将多次操作合并成一个,例:

css 复制代码
var oDiv = document.querySelector('.box');
oDiv.style.padding = '5px';
oDiv.style.border = '1px solid #000';
oDiv.style.margin = '5px';

操作div的3个css属性,分别是padding、border、margin,此时就可以考虑将多次操作合并为一次。

方法一:使用style的cssText:

style 复制代码
oDiv.style.cssText = 'padding:5px; border:1px solid #000; margin:5px;';

方法二:将这几个样式定义给一个类名,然后给标签添加类名:

js 复制代码
<style>
    .pbm{
        padding:5px; 
        border:1px solid #000; 
        margin:5px;
    }
</style>
<script>
    var oDiv = document.querySelector('.box');
    oDiv.classList.add('pbm');
</script>

批量操作DOM

当对DOM有多次操作的时候,需要使用一些特殊处理减少触发回流,其实就是对DOM的多次操作,在脱离标准流后,对元素进行的多次操作,不会触发回流,等操作完成后,再将元素放回标准流。

脱离标准流的操作有以下3种:

  1. 隐藏元素
  2. 使用文档碎片
  3. 拷贝节点

例:下面对DOM节点的多次操作,每次都会触发回流

js 复制代码
var data = [
    {
        id:1,
        name:"商品1",
    },
    {
        id:2,
        name:"商品1",
    },
    {
        id:3,
        name:"商品1",
    },
    {
        id:4,
        name:"商品1",
    },
    // 假设后面还有很多
];
var oUl = document.querySelector("ul");
for(var i=0;i<data.length;i++){
    var oLi = document.createElement("li");
    oLi.innerText = data[i].name;
    oUl.appendChild(oLi);
}

这样每次给ul中新增一个li的操作,每次都会触发回流。

方法一:隐藏ul后,给ul添加节点,添加完成后再将ul显示

js 复制代码
oUl.style.display = 'none';
for(var i=0;i<data.length;i++){
    var oLi = document.createElement("li");
    oLi.innerText = data[i].name;
    oUl.appendChild(oLi);
}
oUl.style.display = 'block';

此时,在隐藏ul和显示ul的时候,触发了两次回流,给ul添加每个li的时候没有触发回流。

方法二:创建文档碎片,将所有li先放在文档碎片中,等都放进去以后,再将文档碎片放在ul中

js 复制代码
var fragment = document.createDocumentFragment();
for(var i=0;i<data.length;i++){
    var oLi = document.createElement("li");
    oLi.innerText = data[i].name;
    fragment.appendChild(oLi);
}
oUl.appendChild(fragment);

方法三:将ul拷贝一份,将所有li放在拷贝中,等都放进去以后,使用拷贝替换掉ul

js 复制代码
var newUL = oUl.cloneNode(true);
for(var i=0;i<data.length;i++){
    var oLi = document.createElement("li");
    oLi.innerText = data[i].name;
    newUL.appendChild(oLi);
}
oUl.parentElement.replaceChild(newUl, oUl);

避免多次触发布局

如下回到顶部的操作:

js 复制代码
goBack.onclick = function(){
    setInterval(function(){
        var t = document.documentElement.scrollTop || document.body.scrollTop;
        t += 10;
        document.documentElement.scrollTop = document.body.scrollTop = t;
    },20)
}

每隔20毫秒都会重新获取滚动过的距离,每次都会触发回流,代码优化如下:

js 复制代码
goBack.onclick = function(){
    var t = document.documentElement.scrollTop || document.body.scrollTop;
    setInterval(function(){
        t += 10;
        document.documentElement.scrollTop = document.body.scrollTop = t;
    },20)
}

只获取一次,每次都让数字递增,避免每次都获取滚动过的距离。

对于页面中比较复杂的动画,尽量将元素设置为绝对定位,操作元素的定位属性,这样只有这一个元素会回流,如果不是定位的话,容易引起其父元素以及子元素的回流。

相关推荐
Мартин.3 小时前
[Meachines] [Easy] Sea WonderCMS-XSS-RCE+System Monitor 命令注入
前端·xss
昨天;明天。今天。4 小时前
案例-表白墙简单实现
前端·javascript·css
数云界4 小时前
如何在 DAX 中计算多个周期的移动平均线
java·服务器·前端
风清扬_jd4 小时前
Chromium 如何定义一个chrome.settingsPrivate接口给前端调用c++
前端·c++·chrome
安冬的码畜日常4 小时前
【玩转 JS 函数式编程_006】2.2 小试牛刀:用函数式编程(FP)实现事件只触发一次
开发语言·前端·javascript·函数式编程·tdd·fp·jasmine
ChinaDragonDreamer4 小时前
Vite:为什么选 Vite
前端
小御姐@stella4 小时前
Vue 之组件插槽Slot用法(组件间通信一种方式)
前端·javascript·vue.js
GISer_Jing4 小时前
【React】增量传输与渲染
前端·javascript·面试
GISer_Jing4 小时前
WebGL在低配置电脑的应用
javascript
eHackyd4 小时前
前端知识汇总(持续更新)
前端