最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 浏览器渲染过程和CRP优化二:CRP优化

    正文概述 掘金(mttt)   2021-06-19   480

    上一篇文章解释了浏览器的渲染过程,简单说了一下什么是CRP,这篇文章来记录一下如何进行CRP优化

    浏览器渲染(解析)页面的过程如下:

    1. 导航:输入URL、DNS解析、建立连接
    2. 响应:发送请求、接收第一次响应的HTML文本
    3. 解析:构建DOM、构建CSSOM、编译javascript
    4. 渲染:样式(Style,构建Render树)、布局计算(Layout,回流/重排)、绘制(Paint,重绘)。

    其中解析和渲染步骤比较重要

    我们将解析和渲染的步骤中关键的五步提取出来,作为浏览器的关键渲染路径,优化关键渲染路径可提高渲染性能

    这五步分别是

    1. 第一步是处理HTML标记,构建DOM树
    2. 第二步是处理CSS,构建CSSOM树。
    3. 第三步是将DOM和CSSOM组合成一个Render树。
    4. 第四步是在依据渲染树计算每个节点的大小和位置。
    5. 最后一步是根据渲染树和回流得到的几何信息,得到节点的绝对像素,将各个节点绘制到屏幕上。

    浏览器渲染过程和CRP优化二:CRP优化

    即:构建DOM->构建CSSOM->构建Render树->回流->重绘。

    详细参考上一篇文章 浏览器渲染过程

    CRP优化之-解析阶段优化

    构建DOM树时遇到样式

    构建DOM树时遇到样式,有以下几种情况 浏览器渲染过程和CRP优化二:CRP优化

    1. 遇到style内嵌样式,GUI直接渲染

      • 所以项目中如果CSS代码量比较少,直接内嵌即可,拉去HTML的时候,同时CSS也回来了,渲染的时候直接就渲染了
      • 但是如果CSS代码比较多,如果还用内嵌,一方面会影响HTML的拉取速度,也不利于代码的维护,此时还是用外链的方式比较好
    2. 遇到link标签(异步),浏览器开辟一个HTTP线程去请求资源文件信息,同时GUI继续向下渲染

      tips:浏览器同时能够发送的HTTP请求是有数量限制的(谷歌:5~7个),超过最大并发限制的HTTP请求需要排队等待,所以HTTP请求一定是越少越好。

    3. 遇到@import(同步),浏览器也是开辟HTTP线程去请求资源,但是此时GUI也暂定了(导入式样式会阻碍GUI的渲染),当资源请求回来之后,GUI才能继续渲染

      所以真实项目中应该避免使用@import

    构建DOM树时遇到js

    遇到 <script src='xxx/xxx.js'> ,有三种解析的方式,分别是 默认,async,defer,默认情况下会阻碍GUI的渲染

    如图所示三种方式的渲染规则:

    浏览器渲染过程和CRP优化二:CRP优化

    (图片中, 'net' 表示请求js文件 , 'execution' 表示执行(解析或者叫渲染)js)

    如果不想阻碍GUI渲染,该怎么办?在标签里加属性 async defer

    • defer:和link是类似的机制了,不会阻碍GUI渲染,当GUI渲染完,才会把请求回来的JS去渲染。
    • async:请求JS资源是异步的(单独开辟HTTP去请求),此时GUI继续渲染;但是一但当JS请求回来,会立即暂停GUI的处理,接下来去渲染JS

    假如我们有多个JS的请求,如果不设置任何属性,肯定是按照顺序请求和执行JS的(依赖关系是有效的);但是如果设置async,谁先请求回来就先渲染谁,依赖关系是无效的;如果使用defer是可以建立依赖关系的。浏览器内部在GUI渲染完成后,等待所有设置defer的资源都请求回来,再按照编写的依赖顺序去加载渲染js。只有写在浏览器的开头或者中间才需要这样写,写在浏览器结尾和默认方式一样

    浏览器渲染过程和CRP优化二:CRP优化 浏览器渲染过程和CRP优化二:CRP优化

    tips:遇到img,音视频,也是和link的机制一样,异步加载,继续渲染GUI。

    总结

    所以真实项目中开发是:

    1. link标签放在头部:一般把link放在页面的头部(是为了在没有渲染DOM的时候,就通知HTTP去请求CSS了,这样DOM渲染玩,CSS也差不多回来了,更有效的利用时间,提高页面的渲染速度)
    2. script标签放在底部:我们一般把JS放在页面的底部,防止其阻碍GUI的渲染,如果不放在底部,我们最好设置上async/defer;

    解析阶段优化方案

    根据以上原理的具体优化方案:

    1. 标签语义化和避免深层次嵌套。加快构建DOM树的过程。

    2. css选择器不要过长。CSS选择器渲染是从右到左的,加快构建CSSOM的过程。

    3. 尽早尽快地把CSS下载到客户端(充分利用HTTP多请求并发机制),将style、link、@import放到顶部

    4. 避免阻塞的JS加载使用async、defer属性,或者将script标签放到底部

    CRP优化之-渲染阶段优化

    渲染阶段主要优化回流和重绘

    虽然现在项目都用vue和react来写,DOM操作已经被极大的简化,很少考虑DOM操作的性能问题,但是当我们自己封装不依赖框架的功能性组件插件的时候,性能仍然是不可忽视的问题

    DOM的重绘和回流Repaint & Reflow

    回流:元素的大小或者位置发生了变化(当页面布局和几何信息发生变化的时候) , 触发了重新布局,导致渲染树重新计算布局。 如

    1. 添加或删除可见的DOM元素;
    2. 元素的位置或尺寸发生变化;
    3. 内容发生变化(比如文本变化或图片被另一个不同尺寸的图片所替代);
    4. 页面一开始渲染的时候(这个无法避免);
    5. 因为回流是根据视口的大小来计算元素的位置和大小的,所以浏览器的窗口尺寸变化也会引发回流

    重绘:元素样式的改变(但宽高、大小、位置等不变)。如 outlinevisibilitycolorbackground-color

    注意:回流-定会触发重绘。而重绘不一定会回流

    避免DOM的回流重绘

    1. 放弃传统操作dom的时代,基于vue/react使用数据影响视图模式。

      mwm/ mvc / virtual dom/ dom diff

    2. css集中改变

      举例:

       <head>
           <style>
           #box {
                   ackground: lightcoral;
                   width: 500px;
                   height: 50px;
               }
           </style>
       </head>
       <body>
           <div id="box"></div>
           <script src="test.js"></script>
       </body>
      

      test.js

      let box = document.querySelector('#box');
      box.style.width = '100px';
      box.style.height = '100px';
      

      如果直接这样写,不会出现样式改变的过程,因为在DOM解析的时候,最后会执行js,执行完js再构建DOM树,这时候box的样式已经变为100px,仅仅会执行一次回流和重绘,就是最开始的那一次。 所以我们这样写:

      test.js

      setTimeout(()=>{
          let box = document.querySelector('#box');
          box.style.width = '100px';
          box.style.height = '100px';
      },1000)
      

      以上操作在老版本的浏览器中会改变两次回流重绘,因为改变了两次样式

      这样写将央视集中改变可以减少次数

      box.style.cssText = "width:100px;height:100px;";
      

      而新版浏览器不会重绘两次,新版浏览器有一个渲染队列机制。接下来再详细说明

    3. 分离css读写操作( 现代的浏览器都有渲染队列的机制)

      连续改变两次样式,在新版浏览器不会重绘两次。新版浏览器有一个渲染队列机制,会把所有要改变的样式依次放在渲染队列里面,然后把渲染队列中的样式渲染一次。

      当修改样式的代码已经没有了或者遇到了获取元素样式的代码,都会刷新渲染队列:即把现有队列中的样式去统一修改和渲染一次,引发一次回流和重绘 浏览器渲染过程和CRP优化二:CRP优化

      获取样式的方式:

      • style.xxx
      • getComputedStyle
      • getBoundingClientRect
      • offsetTopoffsetLeftoffsetWidthoffsetHeightclientTopclientLeftclientWidthclientHeightscrollTopscrollLeftscrollWidthscrollHeightgetComputedStylecurrentStyle ...

      以下会引起两次回流和重绘 浏览器渲染过程和CRP优化二:CRP优化

      如果有一个需求,让其在原始宽度的基础上加100

      box.style.width = parseFloat(window.getComputedStyle(box)['width']) + 100 + 'px';
      box.style.height =  parseFloat(window.getComputedStyle(box)['height']) + 100 + 'px';
      

      改为:

      let prevW = parseFloat(window.getComputedStyle(box)['width']),
          prevH = parseFloat(window.getComputedStyle(box)['height']);0
      box.style.width = prevW + 100 + 'px';
      box.style.height = prevH + 100 + 'px';
      
    4. 元素批量修改

      使用文档碎片减少回流: createDocumentFragment

      以下会触发十次回流与重绘

      let box = document.querySelector('#box'),
          for (let i = 0; i < 10; i++) {
              let span = document.createElement('span');
              span.innerHTML = i + 1;
              box.appendChild(span);
          }
      

      改为

      let box = document.querySelector('#box'),
              frag = document.createDocumentFragment();//文档碎片
          for (let i = 0; i < 10; i++) {
              let span = document.createElement('span');
              span.innerHTML = i + 1;
              frag.appendChild(span);
          }
          box.appendChild(frag)
      

      或者用模板字符串拼接方式:

      let box = document.querySelector('#box'),
          str =  `  ` ;
      for (let i = 0; i < 10; i++) {
          str +=  ` <span>${i+1}</span> ` ;
      }
      box.innerHTML = str;
      
    5. 动画效果应用到position属性为absolute或fixed的元素上(脱离文档流) 因为在重回的时候,是分层冲毁的,每一个文档流单独进行重绘,所以将动画效果单独放到一个文档流上面,可以减少性能开销

    6. CSS3硬件加速( GPU加速) 比起考虑如何减少回流重绘,我们更期望的是,根本不要回流重绘; transfom \ opacity \ filters 这些属性会触发硬件加速,不会引发回流和重绘 可能会引发的坑:过多使用会占用大量内存,性能消耗严重、有时候会导致字体模糊等

    7. 回牺牲平滑度换取速度 每次1像素移动一一个动画,但是如果此动画使用了100%的CPU,动画就会看上去是跳动的,因为浏览器正在与更新回流做斗争。每次移动3像素可能看起来平滑度低了,但它不会导致CPU在较慢的机器中抖动

    8. 避免table布局和使用css的javascript表达式

    利用渲染队列机制做一个轮播图

    写一个轮播图,原理是将第一张图复制一个放在最后,然后再运动到最后的时候,无过渡跳转到第一张图,再继续运行轮播图。代码如下:

    <style>
        * {
            margin: 0;
            padding: 0;
        }
    
        .container {
            position: relative;
            margin: 20px auto;
            width: 800px;
            height: 300px;
            overflow: hidden;
        }
    
        .container .wrapper {
            position: absolute;
            top: 0;
            left: 0;
            z-index: 10;
            display: flex;
            justify-content: flex-start;
            align-items: center;
            width: 4000px;
            height: 100%;
    
            /* 动画 */
            transition: left .3s linear 0s;
        }
    
        .container .wrapper .slide {
            width: 800px;
            height: 100%;
        }
    
        .container .wrapper .slide img {
            display: block;
            width: 100%;
            height: 100%;
        }
    </style>
    
    <div class="container">
        <div class="wrapper">
            <div class="slide">
                <img src="images/1.jpg" >
            </div>
            <div class="slide">
                <img src="images/2.jpg" >
            </div>
            <div class="slide">
                <img src="images/3.jpg" >
            </div>
            <div class="slide">
                <img src="images/4.jpg" >
            </div>
            <!-- 克隆 -->
            <div class="slide">
                <img src="images/1.jpg" >
            </div>
        </div>
    </div>
    
    let container = document.querySelector('.container'),
        wrapper = container.querySelector('.wrapper'),
        step = 0,
        timer;
    
    timer = setInterval(function () {
        step++;
        if (step >= 5) {
            // 立即回到第一张
            wrapper.style.transition = 'left 0s';
            wrapper.style.left =  `0px` ;
            // 运动到第二张
            step = 1;
        }
        wrapper.style.transition = 'left .3s';
        wrapper.style.left =  `-${step*800}px` ;
    }, 2000);
    

    但是这样运行后会有问题,发现运行到最后的时候,突然跳到第二涨,并且还存在过渡。

    原因是与渲染队列机制有关系,当运行step运行到5,浏览器会把四句样式修改放到渲染队列中,最后执行一次回流重绘,这样的话就只有最后两句css起作用了,就从最后一张直接拉到第二张了,而且还有过渡动画。 浏览器渲染过程和CRP优化二:CRP优化

    所以我需要让让其立即刷新一下渲染队列,先跳到第一章,再从第一张过渡到第二张。

    let container = document.querySelector('.container'),
        wrapper = container.querySelector('.wrapper'),
        step = 0,
        timer;
    
    timer = setInterval(function () {
        step++;
        if (step >= 5) {
            // 立即回到第一张
            wrapper.style.transition = 'left 0s';
            wrapper.style.left =   `0px`  ;
            // 运动到第二张
            step = 1;
            // 刷新渲染队列
            wrapper.offsetLeft;
        }
        wrapper.style.transition = 'left .3s';
        wrapper.style.left =   `-${step*800}px`;
    }, 2000);
    

    浏览器渲染过程和CRP优化二:CRP优化

    手动刷新渲染队列

    如果不利用这个机制,那么只能这样写

    浏览器渲染过程和CRP优化二:CRP优化


    起源地下载网 » 浏览器渲染过程和CRP优化二:CRP优化

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元