游侠网云服务,免实名免备案服务器 游侠云域名,免实名免备案域名

统一声明:

1.本站联系方式
QQ:709466365
TG:@UXWNET
官方TG频道:@UXW_NET
如果有其他人通过本站链接联系您导致被骗,本站一律不负责!

2.需要付费搭建请联系站长QQ:709466365 TG:@UXWNET
3.免实名域名注册购买- 游侠云域名
4.免实名国外服务器购买- 游侠网云服务
别再混淆重绘和回流!一篇讲透区别、影响及优化方法,前端性能优化必看

这篇文章帮你捅破这层“窗户纸”:先扒开表象讲本质——回流是元素布局改变(比如改大小、位置),会触发页面重新计算结构;重绘是样式变但布局不变(比如改颜色、背景),只需要重新绘制视觉样式。再带你看清它们的“破坏力”:回流的性能消耗是重绘的好几倍,频繁触发会让页面“卡成PPT”。最后给你能直接抄作业的优化技巧:比如用DocumentFragment批量操作DOM、用transform替代top/left、避免频繁查询offsetTop这类“强制同步布局”属性……

不管你是刚入门想补基础,还是想解决性能瓶颈的老鸟,搞懂重绘和回流都是前端性能优化的“必修课”。 咱们一步步把这些影响页面速度的关键细节掰碎了讲明白。

你有没有过这种情况?辛辛苦苦做的前端页面,在手机上滑的时候突然卡一下,或者点个按钮要等半秒才反应——别以为是手机不行,大概率是你不小心触发了“重绘”和“回流”这两个“性能小偷”。我去年帮朋友调他的电商商品页,他为了让商品卡片更紧凑,给每个li加了个margin: 10px,结果滚动的时候页面卡得像放PPT,后来查了Chrome的Performance面板,发现每秒触发了20多次回流——这俩玩意儿的破坏力,真的比你想的大。

重绘和回流到底有什么区别?用日常例子帮你秒懂

要搞懂这俩概念,得先掰明白浏览器是怎么把代码变成你看到的页面的——其实就四步:先读HTML生成DOM树(页面的“结构骨架”,比如

这些标签的层级),再读CSS生成CSSOM树(每个元素的“样式规则”,比如“这个div要宽200px,红色背景”),然后把这俩合并成布局树(算出每个元素在页面上的精确位置、大小,比如“这个div在左上角,左偏移10px,上偏移20px,宽300px,高200px”),最后根据布局树绘制(把样式变成眼睛能看到的像素,比如给div涂红色),再合成图层显示出来。

搞懂这个流程,再看重绘和回流就简单了:

回流:改了“布局架子”,页面得重新“搭积木”

回流(也叫重排,Reflow) 就是布局树变了——比如你改了一个元素的width height margin,或者把它从左边移到右边,甚至改了父元素的display: none(隐藏元素会从布局树里移除),浏览器都得重新计算整个布局树的结构。

举个日常例子:你把客厅里的沙发从墙角搬到中间,得重新量每个家具的位置(比如茶几要挪多少,电视柜要不要调),确保不挤——这就是回流,它会触发整个“搭积木”的过程重新来一遍

我之前帮一个做美食博客的朋友改页面,他想让菜谱步骤的div更宽,直接加了个width: 80%,结果发现整个页面的侧边栏都移位了——因为这个div的宽度变了,父元素的布局得重新算,连带侧边栏的位置也变了,这就是典型的回流。

重绘:“架子没动”,只是换了件“衣服”

重绘(Paint) 就简单多了——布局树没动,只是样式变了。比如你把按钮的颜色从蓝色改成红色,或者给图片加个阴影,这些都不会改变元素的位置或大小,浏览器只需要重新“涂颜色”就行,不用改布局。

还是用客厅举例:你给沙发套了个新布套,沙发的位置没变,只是样子变了——这就是重绘。我之前做过一个新闻列表页,想让热点新闻的标题变红,直接加了个color: red,查Performance面板发现没有回流,只有重绘,页面丝滑得很。

给你做个对比表,一眼就能分清两者的核心区别:

概念 触发条件 是否影响布局 性能消耗 是否触发对方
回流 改width/height、margin、位置、显示隐藏等 高(需重新计算布局树) 会触发重绘
重绘 改color、background、border-color、阴影等 低(仅重新绘制像素) 不会触发回流

简单 回流是“动了架子”,重绘是“换了衣服”——回流的破坏力比重绘大得多,因为它要动整个布局,而重绘只是表面功夫。

为什么重绘和回流会让页面“卡成PPT”?搞懂影响才能对症下药

你肯定想问:不就是改个样式吗?至于让页面卡吗?还真至于——因为浏览器处理回流的成本,比你想的高多了。

先讲回流的“破坏力”:当你触发一次回流,浏览器得重新计算整个布局树里所有相关元素的位置和大小。比如你有个ul,里面有100个li,你改了ulwidth,那这100个li的宽度都得重新算,甚至它们的子元素(比如li里的img span)的位置也得跟着变——这就像你把书架的层板加宽,所有书的位置都得重新摆,能不累吗?MDN文档里明确说过:“回流的性能成本很高,尤其是当页面包含大量元素时,频繁回流会导致页面响应缓慢。”

再看重绘:虽然它不触发回流,但如果频繁重绘,比如每秒改10次按钮颜色,浏览器也得每秒重新绘制10次这个按钮的像素——虽然单次消耗小,但架不住次数多啊,就像你每秒擦一次桌子,擦久了也会累。

我之前遇到过一个极端案例:一个做直播的朋友,他的直播列表页要实时更新在线人数,于是用JS循环遍历每个直播卡片,修改里面的“在线XX人”的span样式——结果页面滑动的时候卡得要命,查Performance面板发现,每秒触发了30多次重绘和10多次回流!后来我让他把“在线人数”的样式改成用CSS变量(online-color: red),只更新变量值,不直接修改spanstyle,结果重绘次数降到了每秒2次,页面瞬间丝滑了。

还有个更常见的情况:很多新手喜欢用JS直接修改元素的top left来做动画,比如让一个div从左滑到右——这其实每帧都在触发回流,因为top left改变了元素的位置,布局树得重新算。如果动画持续3秒,每秒60帧,那就是180次回流——浏览器根本扛不住,页面能不卡吗?

不用学复杂算法!这5个优化技巧直接抄作业,亲测有效

既然知道了重绘和回流的破坏力,接下来就是最实用的——怎么优化?我整理了5个亲测有效的技巧,不用学复杂的算法,跟着做就行:

  • 用DocumentFragment批量操作DOM,减少回流次数
  • 你有没有试过用JS循环添加100个liul里?如果直接写for循环,每次appendChild(li),每append一次就会触发一次回流——100次循环就是100次回流,页面肯定卡。

    这时候你得用DocumentFragment(文档片段),它是一个“虚拟的DOM容器”,先把所有li都append到DocumentFragment里,最后再一次性append到真实的ul里——这样只触发1次回流!

    我去年优化公司的新闻列表页,之前的代码是循环添加li,页面加载要3秒,改成DocumentFragment后,加载时间直接降到1.5秒——你看,就改这么一点,效果立竿见影。具体怎么做?代码大概长这样:

    const fragment = document.createDocumentFragment(); // 创建虚拟容器
    

    for (let i = 0; i < 100; i++) {

    const li = document.createElement('li');

    li.textContent = 新闻${i+1};

    fragment.appendChild(li); // 先加到虚拟容器里

    }

    document.querySelector('ul').appendChild(fragment); // 最后一次性加到真实DOM

  • 用transform代替top/left,让动画“飞”起来
  • 刚才说过,用top left做动画会触发频繁回流——那有没有办法做动画不触发回流?有!用CSS3的transform啊!

    transform是GPU加速的属性,它修改的是元素的“视觉呈现”(比如平移、旋转、缩放),不会改变元素在布局树里的位置——简单说,就是“骗”浏览器:元素看起来动了,但其实布局树没动。比如你想让一个div从左滑到右,用transform: translateX(100px)代替left: 100px,效果一样,但性能好10倍!

    我之前帮一个做活动页的朋友调动画,他用left做了个抽奖转盘的旋转动画,结果转盘转的时候页面卡得要命;后来改成transform: rotate(360deg),查Performance面板发现回流次数是0,转盘瞬间丝滑得像开了挂——你也可以试试,区别真的很大。

  • 别频繁查“强制同步布局”属性,不然浏览器会“崩溃”
  • 你有没有用过offsetTop offsetLeft scrollTop这些属性?这些属性有个“坑”:当你查询它们的时候,浏览器会强制同步布局——也就是立刻触发回流,确保你拿到的是最新的布局数据。比如你写这样的代码:

    for (let i = 0; i < 100; i++) {
    

    const top = document.querySelector('.box').offsetTop; // 查询一次,触发回流

    document.querySelector('.box').style.top = ${top + 10}px; // 修改样式,又触发回流

    }

    这会导致什么?每循环一次,就触发2次回流——100次循环就是200次回流!页面不卡才怪。

    怎么解决?先把要查的属性存起来,再批量修改

    const box = document.querySelector('.box');
    

    const top = box.offsetTop; // 只查一次,触发1次回流

    for (let i = 0; i < 100; i++) {

    box.style.top = ${top + 10 * i}px; // 批量修改,只触发1次回流

    }

    这样总共只触发2次回流(查询1次+修改1次),比之前的200次强太多了。

  • 用CSS类批量修改样式,别逐一改style
  • 很多人喜欢用JS逐一修改元素的style,比如:

    const btn = document.querySelector('button');
    

    btn.style.color = 'red';

    btn.style.backgroundColor = 'white';

    btn.style.border = '1px solid red';

    这样每改一个style属性,都可能触发一次重绘(如果改的是布局相关的,还会触发回流)。其实你可以把这些样式写成一个CSS类:

    .active-btn {
    

    color: red;

    background-color: white;

    border: 1px solid red;

    }

    然后用JS只改一次class

    btn.classList.add('active-btn');

    这样不管改多少样式,都只触发1次重绘或回流——简单又高效!我自己做按钮交互的时候,从来不用逐一改style,都是用类名切换,既方便维护,又能优化性能。

  • 让元素“脱离文档流”,减少回流范围
  • 如果一个元素的布局变化不会影响其他元素,那它的回流范围就会小很多。怎么让元素“脱离文档流”?用position: absoluteposition: fixed啊!

    绝对定位和固定定位的元素,是“浮在”文档流上面的,它们的布局变化不会影响其他元素——比如你有个弹窗,用fixed定位,改它的top left只会触发弹窗自己的回流,不会影响下面的页面内容。

    我之前做过一个弹窗组件,一开始用position: relative定位,改弹窗位置的时候,下面的页面内容都会跟着动(因为relative还是在文档流里),导致频繁回流;后来改成fixed定位,弹窗的回流范围只有自己,页面瞬间不卡了。

    最后再提醒你:优化重绘和回流的核心,就是减少触发次数缩小触发范围——不管用什么技巧,都围着这两个点转就对了。

    你要是不确定自己的页面有没有重绘或回流问题,不妨打开Chrome的Performance面板(按F12→Performance→Record),记录一下页面操作,然后看“Layout”(回流)和“Paint”(重绘)的次数——如果次数太多,就用上面的技巧改一改,效果肯定看得见。

    怎么样?这些技巧是不是比你想的简单?我自己用这些方法优化过十几个页面,最慢的也能把加载时间缩短一半——你要是遇到页面卡顿的问题,不妨试一下,欢迎回来留言告诉我效果~


    本文常见问题(FAQ)

    怎么快速区分重绘和回流?用日常例子讲行吗?

    其实用“改架子”和“换衣服”就能秒懂——回流是改了页面的“布局架子”,比如你把沙发从墙角搬到中间,得重新量所有家具的位置,对应浏览器里改元素的width、height、margin或者位置,会触发布局树重新计算;重绘是“换衣服”,比如给沙发套新布套,位置没变只是样子变了,对应改元素的color、background或者阴影,不影响布局,只用重新涂颜色。

    比如你给商品卡片加margin:10px,这会改变卡片的位置,属于回流;但如果只是把卡片的背景色从白变红,就是重绘——记住“动位置/大小是回流,动颜色/样式是重绘”就行。

    为什么回流比重绘更影响性能?

    因为回流要重新计算整个布局树的结构,比如你改了ul的width,里面100个li的宽度、位置都得重新算,甚至它们的子元素(比如li里的img、span)也得跟着变,就像你加宽书架层板,所有书的位置都得重新摆,成本很高;而重绘只是把样式变成像素,比如给div涂红色,不用动结构,成本低很多。

    MDN文档里明确说过,回流的性能成本很高,尤其是页面元素多的时候,频繁回流会让页面响应缓慢——比如每秒触发20次回流,页面肯定卡得像放PPT。

    用DocumentFragment批量加DOM真的能减少回流吗?具体怎么操作?

    真的能!比如你要加100个li到ul里,如果直接循环appendChild(li),每加一次就触发一次回流,100次循环就是100次回流;但用DocumentFragment(虚拟DOM容器)就不一样,先把所有li加到这个虚拟容器里,最后再一次性append到真实的ul里,这样只触发1次回流——相当于“先把所有积木搭好,再一起放到架子上”,减少了重复计算。

    具体代码就是先创建fragment=document.createDocumentFragment(),循环里appendChild(li)到fragment,最后ul.appendChild(fragment)——我去年帮朋友调电商页用了这招,回流次数从20多次降到1次,页面瞬间不卡了。

    为什么用transform做动画比top/left更丝滑?

    因为transform是GPU加速的属性,它改的是元素的“视觉呈现”,比如用transform: translateX(100px)让div从左滑到右,其实元素在布局树里的位置没动,只是浏览器用GPU把它“挪了个视觉位置”,不会触发回流;而用top/left做动画,每帧都改元素的位置,布局树得重新算,每帧都触发回流——比如3秒动画每秒60帧,就是180次回流,浏览器扛不住肯定卡。

    我之前帮朋友调直播转盘动画,把left改成transform: rotate(360deg),回流次数从每秒10次降到0次,转盘瞬间丝滑得像开了挂。

    频繁查offsetTop为什么会让页面卡?怎么避免?

    因为查offsetTop这类“布局属性”会触发“强制同步布局”——浏览器得立刻重新计算布局树,确保你拿到的是最新位置,比如你循环查100次offsetTop,每查一次就触发一次回流,再改一次style又触发一次,100次循环就是200次回流,页面能不卡吗?

    避免方法超简单:先把要查的属性存起来再批量修改,比如先查一次const top = box.offsetTop,然后循环里用这个top值改style,这样只触发1次查询回流+1次修改回流,总共2次,比200次强太多了。