前端性能优化篇:重排和重绘

重排(回流 reflow)和重绘(repaint)

参考博客

页面生成过程

图示过程
文字解释
  1. HTML解析器将HTML解析成DOM树

  2. CSS解析器将CSS解析成CSSOM树

  3. 结合两棵树生成一颗渲染树(Render Tree),该过程称为Attachment

  4. 浏览器根据布局方式,在屏幕上“画”出渲染树的所有节点

  5. 然后将布局绘制在屏幕上,显示出整个页面

  6. 第四步和第五步最耗时,合起来就是常说的渲染

重排(回流reflow)

概念

  • 当DOM的变化影响了元素的几何信息(元素的的位置和尺寸大小),浏览器需要重新计算元素的几何属性,将其安放在界面中的正确位置,这个过程叫做重排。
  • 简单的说就是重新生成布局,重新排列元素。

什么情况会发生重排?

  • 页面初始渲染,这是开销最大的一次重排

  • 增删可见的DOM元素

  • 改变元素位置

  • 改变元素尺寸:元素总尺寸=宽高(内容填充)+内边距+边框+外边距

  • 改变元素内容:文字数量,图片大小等

  • 改变元素字体大小

  • 改变浏览器窗口尺寸:resize事件发生时

  • 激活CSS伪类

  • 增加CSS伪元素

  • 设置 style 属性的值,每一次设置都会触发一次reflow

  • 查询某些属性或调用某些计算方法:offsetWidth、offsetHeight等,除此之外,当我们调用 getComputedStyle方法,或者IE里的 currentStyle 时,也会触发重排,原理是一样的,都为求一个“即时性”和“准确性”。

  • 常见引起重排属性和方法
    width height margin padding
    display border-width border position
    overflow font-size vertical-align min-height
    clientWidth clientHeight clientTop clientLeft
    offsetWudth offsetHeight offsetTop offsetLeft
    scrollWidth scrollHeight scrollTop scrollLeft
    scrollIntoView() scrollTo() getComputedStyle()
    getBoundingClientRect() scrollIntoViewIfNeeded()

重排影响范围

全局范围(避免)
  • 从根节点html开始对整个渲染树进行重新布局。
局部范围
  • 对渲染树的某部分或某一个渲染对象进行重新布局
  • 将一个DOM元素的宽高等几何信息定死,在这个元素内部触发重排,只会重新渲染该元素内部的元素,不会影响到外界

重绘(repaint)

概念

  • 当一个元素的外观发生改变,但没有改变布局,重新把元素外观绘制出来的过程。

什么情况下会发生重绘?

  • 属性:
    color border-style visibility background
    text-decoration background-image background-position background-repeat
    outline-color outline outline-style border-radius
    outline-width box-shadow background-size

重排和重绘比较

  • 两者都会影响性能,但重排比重绘工作量大,会使我们付出高额的性能代价

  • 重绘不一定导致重排,重排一定会导致重绘

    • 重绘单单改变元素的外观,不会引起网页重新生成布局

    • 当浏览器完成重排之后,将会重新绘制受到此次重排影响的部分。

优化方案

减少重排范围

  • 尽量以局部布局的形式组织html结构,尽可能小的影响重排的范围(BFC)
  • 不要使用 table 布局,可能很小的一个小改动会造成整个 table 的重新布局。那么在不得已使用table的场合,可以设置table-layout:auto;或者是table-layout:fixed这样可以让table一行一行的渲染,这种做法也是为了限制reflow的影响范围。

减少重排次数

1. 样式集中改变
  • 对于页面来说,明智且可维护的做法是更改类名而不是修改样式。

  • 利用style.cssText或者classname进行批量修改

    • document.getElementById("d1").style.cssText = "color:red; font-size:13px;";
      
      document.getElementById("d1").classList.replace=('previousClassname','newClassname')
      
      复制代码
2. 分离读写操作(避免强制刷新)
  • DOM 的多个读操作(或多个写操作),应该分别放在一起。不要两个读操作之间,加入一个写操作。

    • // bad 强制刷新 触发四次重排+重绘
      div.style.left = div.offsetLeft + 1 + 'px';
      div.style.top = div.offsetTop + 1 + 'px';
      div.style.right = div.offsetRight + 1 + 'px';
      div.style.bottom = div.offsetBottom + 1 + 'px';
      
      
      // good 缓存布局信息 相当于读写分离 触发一次重排+重绘
      var curLeft = div.offsetLeft;
      var curTop = div.offsetTop;
      var curRight = div.offsetRight;
      var curBottom = div.offsetBottom;
      
      div.style.left = curLeft + 1 + 'px';
      div.style.top = curTop + 1 + 'px';
      div.style.right = curRight + 1 + 'px';
      div.style.bottom = curBottom + 1 + 'px';
      
      复制代码
为什么读写分离后只触发一次重排
  • 这都得益于浏览器的渲染队列机制

  • 当我们修改了元素的几何属性,导致浏览器触发重排或重绘时。它会把该操作放进渲染队列,等到队列中的操作到了一定的数量或者到了一定的时间间隔时,浏览器就会批量执行这些操作。

    • div.style.left = '10px';
      div.style.top = '10px';
      div.style.width = '20px';
      div.style.height = '20px';
      // 这时渲染队列有上面四个写操作
      console.log(div.offsetLeft);
      // 在第一个console的时候,涉及div.offsetLeft关键样式,浏览器会立即执行渲染队列的任务,把之前上面四个写操作的渲染队列都给清空了。
      // 因为队列中,可能会有影响到这些值的操作(即console.log操作要打印最新的div.offsetLeft的值),为了给我们最精确的值,浏览器会立即重排+重绘,所以队列全部清空。
      // 注意: 涉及关键属性的操作,无论何时浏览器都会立即执行渲染队列的任务,即使该值与你操作中修改的值没关联。
      console.log(div.offsetTop);
      console.log(div.offsetWidth);
      console.log(div.offsetHeight);
      // 剩下的console,因为渲染队列本来就是空的,所以并没有触发重排,仅仅拿值而已。
      复制代码
    • 强制刷新队列的操作请求

      • offsetTop, offsetLeft, offsetWidth, offsetHeight
        scrollTop, scrollLeft, scrollWidth, scrollHeight
        clientTop, clientLeft, clientWidth, clientHeight
        getComputedStyle(), 或者 IE的 currentStyle
3. 将要修改的DOM元素离线操作
1. 使用 display:none
  • 一旦我们给元素设置 display:none 时(只有一次重排重绘),元素便不会再存在在渲染树中,相当于将其从页面上“拿掉”,我们之后的操作将不会触发重排和重绘,添加足够多的变更后,通过 display属性显示(另一次重排重绘),通过这种方式即使大量变更也只触发两次重排。
2. 创建碎片
  • 通过 documentFragment 创建一个 dom 碎片,在它上面批量操作 dom,操作完成之后,再添加到文档中,这样只会触发一次重排。
3. 复制节点
  • 复制节点【cloneNode(deep)】,在副本上工作,然后替换【replaceChild(newNode,oldNode)】它!
4. 脱离文档流
  • 使用绝对定位会使的该元素单独成为渲染树中 body 的一个子元素,重排开销比较小,不会对其它节点造成太多影响。当你在这些节点上放置这个元素时,一些其它在这个区域内的节点可能需要重绘,但是不需要重排。
5. 优化动画
  • 可以把复杂动画效果应用到 position属性为 absolutefixed 的元素上,这样对其他元素影响较小。
  • 动画效果还应牺牲一些平滑,来换取渲染速度:比如实现一个动画,以1个像素为单位移动这样最平滑,但是Layout就会过于频繁,大量消耗CPU资源,如果以3个像素为单位移动则会好很多
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享