阅读 203

css - 重绘回流

chrome performance看浏览器渲染过程

先看一个非常简单的页面代码

<!Doctye html>
<html>
<head>
</head>
<body>
  <div> 
    Test dom load.
  </div>
</body>
</html>
复制代码

然后打开chrome performence查看页面的渲染过程: image.png

  • Send Request :发送网络请求时触发
  • Receive Response:响应头报文到达时触发
  • Receive Data:请求的响应数据到达事件,如果响应数据很大(拆包),可能会多次触发该事件
  • Finish Loading:网络请求完毕事件
  • Parse HTML:浏览器执行HTML解析
  • Update Layer Tree :更新图层树
  • Paint:确定渲染树上的节点的大小和位置后,便可以对节点进行涂鸦(paint)
  • Composite Layers: 合成层,当渲染树上的节点涂鸦完毕后,便生成位图(bitmap),浏览器把此位图从CPU传输到GPU

Parse Html(without css and js)


<!Doctye html>
<html>
<head>
</head>
<body>
  <div class='div'> 
    Test dom load.
  </div>
  <script type="text/javascript">
    console.log('resolve body JavaScript');
   
    window.addEventListener('load',function(){
      console.log('window load');
    })

    document.addEventListener('readystatechange',function(){
      console.log('document ' + document.readyState);
    })

    document.addEventListener('DOMContentLoaded',function(){
      console.log('document DOMContentLoaded');
    })
    //document 没有load事件??
    document.addEventListener('load',function(){
      console.log('document load');
    })
  </script>
</body>
</html>

复制代码

image.png

然后我们按照时间线从左到右来看一下里面的事情(先说一下,这个图怎么看,下面的内容是上面内容的调用,这里也就是上面带图中带有文字说明的内容都是在ParseHtml的周期内调用的内容):

readystatechange(第一个)

说这个的时候就需要说一个事件DOM readystatechange, readyState 属性描述了文档的加载状态,在整个加载过程中 document.readyState会不断变化,每次变化都会触发readystatechange事件。 ​

readyState 有以下状态:

  • loading 加载document 仍在加载。
  • interactive 互动文档已经完成加载,文档已被解析,但是诸如图像,样式表和框架之类的子资源仍在加载。
  • complete 完成DOM文档和所有子资源已完成加载。状态表示 load 事件即将被触发。

那么这里的事件执行的是哪一步呢?

这里执行的是interactive。因为这个事件后面紧跟着的是 DOMContentLoaded事件,而且如果你亲自访问这个页面去看一下,在parseHtml前面还有一次readystatechange,那里应该是loading 。

DOMContentLoaded (构建 DOM 树成功)

DOM树渲染完成时触发DOMContentLoaded事件,此时可能外部资源还在加载。这里表示DOM树加载完成。

Recalculate Style(构建 CSSOM 树)

从文字的字面意义理解也就是重新计算样式。为什么是 Re-caculate Style 呢?这是因为浏览器本身有 User Agent StyleSheet,所以最终的样式是我们的样式代码样式与用户代理默认样式覆盖/重新计算得到的。这里也是在构建CSSOM树。

readystatechange(第二个)

从第一个事件的地方可有了解到这里执行的是complete,表示页面上的DOM树和CSSOM树已经形成并且合并成Render树。此时页面上所有的资源都已经加载完成。其实从后面的load事件也可以看出来

load事件

所有的资源全部加载完成会触发window 的 load事件。

pageshow事件

当一条会话历史记录被执行的时候将会触发页面显示(pageshow)事件。(这包括了后退/前进按钮操作,同时也会在load事件触发后初始化页面时触发)。

以下示例将会在控制台打印由前进/后退按钮以及load事件触发后引起的pageshow事件:

window.addEventListener('pageshow', function(event) {
    console.log('pageshow:');
    console.log(event);
});
Layout
复制代码

Layout

将渲染树上的节点,根据它的高度,宽度,位置,为节点生成盒子(layout)。为元素添加盒子模型。上图中有两个layout,二者之间的不同是Nodes That Need Layout 1/5 of 5 这里的5代表应该是页面上的5个node(文本内容是文本节点),但是对于这个1,还是没有一个明确的说明,但是影响不大,毕竟还是属于layout。 ​

其实看到这里:我们想一下ParseHtml做了哪些内容: 构建DOM树 -> 构建CSSOM树 -> 构建Render树 -> 布局layout

Parse Html(with css and js)

<!Doctye html>
<html>
<head>
  <style type="text/css">
    .div {
      color: blue
    }
  </style>
</head>
<body>
  <div class='div'> 
    Test dom load.
  </div>
  <script type="text/javascript">
    var a = 1 + 1;
  </script>
</body>
</html>
复制代码

image.png 这里就只说和上面图中不一样的地方: ​

首先是多个两个黄颜色的js相关的内容,一个叫做Evaluate Script(加载js),另一个是Compile Script(js预编译处理,可以查看文章,这里也已经对js文件执行了)。 ​

第二个不同的地方是从第二个readystatechange事件起一直到layout都已经不在ParseHtml内部完成了。这里我简单的去做了一个测试,只有css或者js存在的情况下,还是和现在一样的结果,是因为css或者js阻塞了整个页面的渲染过程,因为js和css都有可能对标签进行样式的设置,从而影响了layout的执行。

测试回流和重绘

下面会用到定时器,因为现代浏览器队现代浏览器会对频繁的回流或重绘操作进行优化,可能会存在批处理,看不到效果。 ​

回流

当Render Tree中部分或全部元素的尺寸、结构、或某些属性发生改变时,浏览器重新渲染部分或全部文档的过程称为回流。


<!Doctye html>
<html>
<head>
  <style type="text/css">
    .div {
      color: blue
    }
  </style>
</head>
<body>
  <div class='div'> 
    Test dom load.
  </div>
  <script type="text/javascript">
    var a = 1 + 1;
    document.getElementsByTagName('div')[0].style.marginTop = '20px'

    setTimeout(() => {
      document.getElementsByTagName('div')[0].style.marginTop = '40px'
    }, 1000)
  </script>
</body>
</html>

复制代码

image.png

重绘

当页面中元素样式的改变并不影响它在文档流中的位置时(例如:color、background-color、visibility等),浏览器会将新样式赋予给元素并重新绘制它,这个过程称为重绘。 ​


<!Doctye html>
<html>
<head>
  <style type="text/css">
    .div {
      color: blue
    }
  </style>
</head>
<body>
  <div class='div'> 
    Test dom load.
  </div>
  <script type="text/javascript">
    var a = 1 + 1;
    document.getElementsByTagName('div')[0].style.color = 'green'

    setTimeout(() => {
      document.getElementsByTagName('div')[0].style.color = 'red'
    }, 1000)
  </script>
</body>
</html>

复制代码

image.png

结论

回流造成:Update Layer Tree -》layout -》 Paint -》Composite Layers 重绘造成:Update Layer Tree -》Paint -》Composite Layers ​

回流会造成重绘,重绘不会造成回流。

导致回流的操作

juejin.cn/post/684490… 会导致回流的操作:

  • 页面首次渲染
  • 浏览器窗口大小发生改变
  • 元素尺寸或位置发生改变
  • 元素内容变化(文字数量或图片大小等等)
  • 元素字体大小变化
  • 添加或者删除可见的DOM元素
  • 激活CSS伪类(例如::hover)
  • 查询某些属性或调用某些方法

一些常用且会导致回流的属性和方法: clientWidth、clientHeight、clientTop、clientLeft offsetWidth、offsetHeight、offsetTop、offsetLeft scrollWidth、scrollHeight、scrollTop、scrollLeft scrollIntoView()、scrollIntoViewIfNeeded() getComputedStyle() getBoundingClientRect() scrollTo()

如何避免

下面的内容来源:juejin.cn/post/684490… 【掘金 - 腰花】

CSS

  • 避免使用table布局。
  • 尽可能在DOM树的最末端改变class。
  • 避免设置多层内联样式。
  • 将动画效果应用到position属性为absolute或fixed的元素上。
  • 避免使用CSS表达式(例如:calc())。

JavaScript

  • 避免频繁操作样式,最好一次性重写style属性,或者将样式列表定义为class并一次性更改class属性。
  • 避免频繁操作DOM,创建一个documentFragment,在它上面应用所有DOM操作,最后再把它添加到文档中。
  • 也可以先为元素设置display: none,操作结束后再把它显示出来。因为在display属性为none的元素上进行的DOM操作不会引发回流和重绘。
  • 避免频繁读取会引发回流/重绘的属性,如果确实需要多次使用,就用一个变量缓存起来。
  • 对具有复杂动画的元素使用绝对定位,使它脱离文档流,否则会引起父元素及后续元素频繁回流。

文章分类
前端
文章标签