Web性能优化-重绘与回流

2018-10-11  本文已影响0人  Ethan__Hunt

先看概念介绍:

重绘 Paint

当render tree中的一些元素需要更新属性,而这些属性只是影响元素的 外观风格,而不会影响布局的,比如background-color。则就叫称为重绘。

咋看页面是不是在重绘? 重绘理解起来还不如重画,翻译成中文就是再画一遍.
在Chrome的开发者工具中点 竖着的三个点 . 然后点More tools,然后选Rendering ,

然后 重绘.png
把这个checkbox 点上, 然后再看页面 中如果发生 重绘 的地方,就会用绿色闪过. 这个好观察 , 找一个timer 发现一秒变一次. 比如在淘宝上的这个 .
image.png

当然更直观一些的, 可以直接拖动滚动条. 因为很多资源没有加载出来或者 页面大小变更,触发重绘, 绿色的地方更明显.

回流 Layout

当render tree中的一部分(或全部)因为元素的规模尺寸,布局,隐藏等改变而需要重新构建。这就称为回流(reflow).Layout
页面布局几何属性 改变时就需要回流.
页面布局:比如淘宝搜商品, 越往下滑,商品越多,这时候布局改变
几何属性:比如一个图片点击之后变大.

Note: 回流必将引起重绘,而重绘不一定会引起回流
回流重点在 位置, 重绘重点在 效果.
换了个位置, 需要在新的地方再画一个.

触发页面重布局的属性

触发页面重布局的属性.png

只触发重绘的属性


只触发重绘的属性.png

新建DOM的过程

  1. 获取DOM后分割为多个图层
  2. 对每个图层的节点计算样式结果(Recalculate style--样式重计算)
  3. 为每个节点生成图形和位置(Layout--回流和重布局)
  4. 将每个节点绘制填充到图层位图中(Paint Setup和Paint--重绘)
  5. 图层作为纹理上传至GPU
  6. 符合多个图层到页面上生成最终屏幕图像(Composite Layers--图层重组)

通过Performance截取了一部分的淘宝页面中的轮播切换的位置 .就是一个图片切换到另外一个图片 ,浏览器做了些什么 ?


Recalculate style.png Layout.png
Update Layer Tree.png paint.png 最后Composite Layers.png

GPU? 啥玩意是GPU? 我就听说过CPU , 我刚开始还以为是写错了.....
GPU 简写来自 Graphic Processing Unit ,中文翻译为图形处理器. 简单点理解, 都知道CPU在电脑中的用处 , GPU在显卡中的作用 类似于CPU的作用. 专门的图形的核心处理器.

图层? 什么叫图层 . 中文一层一层的图 ? 英文叫layers .
还是Chrome 开发者工具, 点三个点, 然后More Tools ,然后选Layers.
能看见页面中的图层, 我试了一下 发现, 百度的首页里边一个图层都没有.
而淘宝的首页里边有N个图层 以至于 在点开每个图层的时候 非常卡.
那么有个问题 图层咋来的 ?
从Dom 元素变为 图层方式:

  1. CSS设置属性 transform:translateZ(0)
  2. CSS设置属性 will-change: transform

频繁重绘回流的DOM元素单独作为一个独立图层,那么这个DOM元素的重绘和回流的影响只会在这个图层

比如video ,早期电影不就是一帧一帧的图片么....每次都会重绘.这个给他放在一个独立的图层中,效果更好. 看一下腾讯视频的video
貌似行成图层的原因还挺多的 .
比如这个 .


image.png

这是因为加了css 样式 ,就是上边说的第一个


image.png

那么对于video 是为啥就成了图层了 ?


image.png

意思也就是只要是个video 元素 他就是一个图层了.

当然应该还有其他原因生成这些图层 就不举例子了 .还挺多的. 自己点点看吧

Chrome创建图层的条件

那么知道了之后怎么用?

  1. 避免使用触发重绘.回流的css属性
  2. 将重绘回流的影响范围限制在单独的图层之内

实战优化点 这个更重要

  1. 用translate替代top改变 (优化测试: 详细例子在这里 )
  2. 用opacity替代visibility ,也就是用 opacity 0 或者1 来替代visibility
  3. 不要一条一条地修改 DOM 的样式,预先定义好 class,然后修改 DOM 的 className (常用)
  4. 把 DOM 离线后修改,比如:先把 DOM 给 display:none (有一次 Reflow),然后你修改100次,然后再把它显示出来
  5. 不要把 DOM 结点的属性值放在一个循环里当成循环里的变量 ,比如offsetHeight offsetWidth ,涉及到什么回流的缓存机制的. 记住吧.
  6. 不要使用 table 布局,可能很小的一个小改动会造成整个 table 的重新布局.之前面试过Oracle的时候问过, 为啥不用table 布局了? a:对于搜索引擎的收录更加友好。b:w3c规范. c:占用空间小 d:改样式费劲 .e: 再加上回流这个.
  7. 动画实现的速度的选择.(别设置个1ms就动一次)
  8. 对于动画新建图层,
  9. 启用 GPU 硬件加速

测试例子:

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title></title>
    <style>
        #rect{
            width:100px;
            height:100px;
            background-color: blue;
            position:absolute;
        }
    </style>
</head>
<body>
    <div id="rect"></div>
    <script>
        setTimeout(function(){
            document.getElementById("rect").style.top = '100px';
        },2000)
    </script>
</body>
</html>

chrome中效果为


image.png

相比之下减少了Layout 也就是回流的过程

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title></title>
    <style>
        #rect{
            transform: translateY(0);
            width:100px;
            height:100px;
            background-color: blue;
        }
    </style>
</head>
<body>
    <div id="rect"></div>
    <script>
        setTimeout(function(){
            document.getElementById("rect").style.transform = 'translateY(100px)'
        },2000)
    </script>
</body>
</html>
减少了Layout.png

测一下visibility 和 opacity
显示visibility ,会调用重绘.也就是Paint 就是第三个红柱子
document.getElementById("rect").style.visibility = 'hidden';

visibility.png

然后是opacity,然后发现, 我靠,不是说opacity效率更高么怎么 不光重绘 还有回流了?
document.getElementById("rect").style.opacity = '0';

opacity.png

关于这个 说是什么有阿尔法通道的还不是太理解. 先放着.
然后把opacity里边的元素放到图层里边
效果是这样

image.png

总体跟visibility差不多. 这一点先放着.暂时理解不是特别深.

测试第三点

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title></title>
    <style>
        .rect{
            width:100px;
            height:100px;
            background-color: blue;
        }
        .action{
            top:100px;
            width:150px;
            height:150px;
            background-color: red;
            color:blue;
        }
    </style>
</head>
<body>
<div class="rect"></div>
<script>
    setTimeout(function(){
        document.getElementsByClassName("rect")[0].className = 'action'
    },2000)
</script>
</body>
</html>

效果如下 .只有一次.


用className.png

多次改变style的值

效果是几乎差不多的.也没有多次的Layout , 猜测是chrome 内部已经可以处理这样的问题.


image.png

测试第四点: 相当于做一个离线 . 看效果

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title></title>
    <style>
        .rect{
            width:100px;
            height:100px;
            background-color: blue;
            display:none;
        }
    </style>
</head>
<body>
<div class="rect"></div>
<script>
    setTimeout(function(){
        console.log("change");
        document.getElementsByClassName("rect")[0].style.opacity = "0";
        document.getElementsByClassName("rect")[0].style.width = "150px";
        document.getElementsByClassName("rect")[0].style.height = "150px";
        document.getElementsByClassName("rect")[0].style.top = "100px";
        document.getElementsByClassName("rect")[0].style.backgroundColor = "red";
        document.getElementsByClassName("rect")[0].style.color = "blue";
        document.getElementsByClassName("rect")[0].style.width = "150px";
        document.getElementsByClassName("rect")[0].style.height = "150px";
        document.getElementsByClassName("rect")[0].style.top = "100px";
        document.getElementsByClassName("rect")[0].style.opacity = "1";
    },2000)
</script>
</body>
</html>

看下浏览器做了些啥 ?


image.png

接着是2秒之后让他显示出来


image.png

也就是说 如果你不显示出来, 浏览器就不会回流重绘.

比较第五点的两种写法 :
第一种写法:

    var doms = [];//通过选择器选择出一个dom元素的数组
    var domTop = [];//当前的可视区域的高度来计算这些doms元素的top值 也就是他们的位置
    for(var i = 0 ; i< doms.length; i++){
        var clientHeight = document.body.clientHeight;
        domTop.push(clientHeight + i * 100);
    }

第二种写法

    var doms = [];//通过选择器选择出一个dom元素的数组
    var domTop = [];//当前的可视区域的高度来计算这些doms元素的top值 也就是他们的位置
    var clientHeight = document.body.clientHeight;
    for(var i = 0 ; i< doms.length; i++){
        domTop.push(clientHeight + i * 100);
    }

第一种写法,在循环中会一直回去当前的可视区域的高度,因为每次都需要取最新的, 但是浏览器本身有一个缓冲区, 也就是说每次都需要冲完缓冲区,然后取一个最新的. 这样会破坏浏览器的缓冲机制 .

上一篇下一篇

猜你喜欢

热点阅读