likes
comments
collection
share

还搞不懂回流和重绘嘛!

作者站长头像
站长
· 阅读数 23

浏览器渲染机制

还搞不懂回流和重绘嘛!

  • 浏览器将下载好的HTML源码解析成DOM树,根节点是<html>documentElement
  • 浏览器解析CSS,忽略掉无法识别的CSS代码,CSS代码包括浏览器默认样式、开发者的CSS文件还有嵌入HTML的style属性里的样式。
  • 构造渲染树(render tree)。渲染树不同于DOM树,它能够获取到节点样式,仅包含需要渲染的节点,任何不可见的元素(display:none<head>)都不会出现在渲染树中。另外如果一个<p>标签内有多行元素,在渲染树中每行都会看做是一个节点。
  • 渲染树构造完成后浏览器就可以在屏幕上绘制界面了。

渲染机制例子

  • HTML源码
    <html>
      <head>
      <title>Beautiful page</title>
      </head>
      <body>
          
      <p>
          Once upon a time there was 
          a looong paragraph...
      </p>
      
      <div style="display: none">
          Secret message
      </div>
      
      <div><img src="..." /></div>
      ...
      
      </body>
    </html>
    
  • DOM树
    documentElement (html)
      head
          title
      body
          p
              [text node]
      	
          div 
              [text node]
      	
          div
              img
      	
          ...
    
  • 渲染树
    root (RenderView)
      body
          p
              line 1
              line 2
              line 3
              ...
              
          div
              img
          
      ...
    

回流和重绘(reflow&repaint

首先可以确定的是网页肯定至少有一次布局和绘制,除非是一个空白页面。

什么是回流和重绘

  • 部分或全部渲染树需要重新验证,节点大小需要重新计算,这称之为回流(reflow,relayout)。至少有一次回流发生,即页面的初始布局。
  • 页面部分因为样式的改变而需要更新修改结果,比如说背景颜色、字体颜色、可见性(visibility:hidden),页面可见的更新称之为重绘

什么会触发回流和重绘

  • 增加、删除、修改节点
  • 隐藏DOM节点:使用display:none(回流并重绘);使用visibility:hidden(仅重绘,因为没有布局大小上的改变)
  • 移动等动画作用于DOM节点
  • 添加样式表,并修改样式
  • 用户操作比如放缩窗口大小、修改字体大小、滑动滚动条。

例子:

var bstyle = document.body.style; // 缓存
 
bstyle.padding = "20px"; // 回流,重绘
bstyle.border = "10px solid red"; // 回流,重绘
 
bstyle.color = "blue"; // 仅仅重绘,没有位置大小改变
bstyle.backgroundColor = "#fad"; // 重绘
 
bstyle.fontSize = "2em"; // 回流,重绘
 
// 创建新元素 - 回流,重绘
document.body.appendChild(document.createTextNode('dude!'));

如何减少回流和重绘

因为除了一些必要的回流和重绘,其他的回流和重绘是不必要甚至是非常浪费资源的,因此我们要尽量避免这种回流和重绘,那么如何避免呢?

浏览器的自动优化

浏览器为回流和重绘设置一个队列,队列里面保存需要修改的内容,浏览器可以批量处理。时间达到限制或者改变数目达到限制,多次引起回流的改变可以绑定成一个,这样只有一次回流。

但有一些改变或请求可以强制浏览器清空队列并执行重绘,如下:

  • offsetTop,offsetLeft,offsetWidth,offsetHeight(只读,当前元素和最近relative祖先元素的left距离,当前元素的可见高度)
  • scrollTop,scrollLeft,scrollWidth,scrollHeight
  • clientTop,clientLeft,clienWidth,clientHeight
  • getComputedStyle(),或者currentStyle in IE

因为浏览器必须返回给你这些变量的最新的值,因此浏览器必须清空修改队列,并执行相应的更新操作。

常用避免策略

  • 不要独立地单独修改样式,修改class或者修改cssText属性
      // bad
      var left = 10,
          top = 10;
      el.style.left = left + "px";
      el.style.top  = top  + "px";
      
      // better 
      el.className += " theclassname";
      
      // 或者动态计算
      
      // better
      el.style.cssText += "; left: " + left + "px; top: " + top + "px;";
    
  • 不要在动态的DOM树上批量修改
    • 用一个临时进行修改,最终将修改的变量加入到DOM树中。
    • 克隆准备修改的节点,在克隆节点上修改,然后替换原来的节点
    • 隐藏原来的节点(display:none,1次回流,1次重绘),在这个节点上进行批量修改,重新显示这个节点(1次回流,1次重绘)。
  • 不要频繁计算样式,不要让浏览器的队列频繁刷新执行修改,比如不要在循环中使用上面提到的那些变量。应该使用一个缓存变量暂时存储。
      // no-no!
      for(big; loop; here) {
          el.style.left = el.offsetLeft + 10 + "px";
          el.style.top  = el.offsetTop  + 10 + "px";
      }
      
      // better
      var left = el.offsetLeft,
          top  = el.offsetTop
          esty = el.style;
      for(big; loop; here) {
          left += 10;
          top  += 10;
          esty.left = left + "px";
          esty.top  = top  + "px";
      }
    
  • 通常来说,可以考虑当前的修改会让渲染树进行多少计算,然后针对性的进行优化。

    比如说将一个准备进行动画的节点设为postion:absolute使之成为<body>的子节点,这就不会影响大量的其他节点

总结

  • 渲染树是DOM树的可见部分
  • 渲染树的重新计算叫做回流
  • 重新计算后将修改绘制在屏幕上叫做重绘
  • 回流一定重绘
  • 重绘不一定回流
  • 需要批量修改样式时使用临时变量,或使目标节点暂时脱离渲染树,或使目标节点减少在渲染树上和其他节点的关系,这样可以一定程度减少回流和重绘
转载自:https://juejin.cn/post/7304538151480705062
评论
请登录