浏览器渲染

从浏览器角度谈渲染

  • 之前更多的优化是从页面资源的数量,传输是否采用了压缩,JS、cSS,是否进行了精简,缓存有没有合理的使用
  • 这次从另一个角度谈页面的渲染,浏览器是如何工作的,要把页面渲染出来,浏览器需要做什么,哪些过程比较耗时
  • 浏览器工作原理浏览器工作原理
    • 加载HTML
    • 解析HTML,生成DOM
    • 加载CSS,JS
    • 解析CSS,生成CSSOM
    • JS引擎执行JS
    • 合并DOM和CSSOM,生成Render Tree
    • 根据Render Tree 进行布局
    • 绘制每个层中的元素
    • 合并图层
  • 方案:加快完成DOM+CSSOM-Layout-Paint-Composite的整个过程
    • 分割css
      对不同的浏览终端,同一终端的不同模式,提供不同的规则(通俗讲屏幕大小和宽高比)把媒体查询放在link上,根据终端情况加载不同的css文件
  • css规则的优先级
    css权重计算的无形增加
    css选择器解析从右往左还是从左往右??
    过多的嵌套规则,会导致复杂的,无必要的深层次规则(SASS,LESS开发过程中尤为明显)
    css规则越复杂,构建Render Tree时,浏览器花费的时间越长
  • 使用GPU加速
    很多的动画定时的执行,会导致浏览器的重新布局

          @keyframes my {
         20% {
          top: 10px;
          }
    
      50% {
          top: 120px;
      }
    
      80% {
          top: 10px;
      }
    } 
    

    这些执行可以放到GPU加速执行

     @keyframes my {
      20% {
          transform: translateY(10px);
      }
    
      50% {
          transform: translateY(120px);
      }
    
      80% {
          transform: translateY(10px);
      }
    }
    
jQuery 不能避免 layout thrashing (有人喜欢将其翻译为“布局颠簸”,会导致多余relayout/reflow),因为它的代码不仅仅用于动画,它还用于很多其他场景。 

jQuery的内存消耗较大,经常会触发垃圾回收。而垃圾回收触发时很容易让动画卡住。

jQuery使用了setInterval而不是 reqeustAnimationFrame(RAF),因为 RAF 会在窗口失去焦点时停止触发,这会导致jQuery的bu    
**setTimeOut和FAF(单人淋浴间和大澡堂)**    

var startingTop = 0;
        setInterval(function() {
            element.style.top = (startingTop += 1/60);
        }, 16);


        function tick () {
            element.style.top = (startingTop += 1/60);
        }
        window.requestAnimationFrame(tick);


requestAnimationFrame
setTimeout间隔设置过小,显示器16.7ms刷新间隔之前发生了其他绘制请求(setTimeout),导致所有第三帧丢失,继而导致动画断续显示(堵车的感觉),这就是过度绘制带来的问题。不仅如此,这种计时器频率的降低也会对电池使用寿命造成负面影响,并会降低其他应用的性能。

而requestAnimationFrame就是为了这个而出现的。所做的事情很简单,跟着浏览器的绘制走,如果浏览设备绘制间隔是16.7ms,那我就这个间隔绘制;如果浏览设备绘制间隔是10ms, 我就10ms绘制。这样就不会存在过度绘制的问题,动画不会掉帧

优势:向下兼容 、相比于css3动画应用更广、动画效果更多

CSS transition 的动画逻辑是由浏览器来执行,所以它的性能能够比 jQuery 动画好。它的优势体现在:

通过优化 DOM 操作,避免内存消耗来减少卡顿
使用与 RAF 类似的机制
强制使用硬件加速 (通过 GPU 来提高动画性能)

  • 异步JavaScript
    js执行会阻塞DOM构建的过程,Js中可能存在dom操作 async异步

    js可能会不断的重新布局,重新绘制

    1.访问元素的某些属性  
    2.通过JavaScript修改元素的CSS属性  
    3.在onScroll中做耗时的任务  
    4.在其他Event Handler中做耗时的任务  
    5.图片的预处理  
    6.过多的动画  
    7.过多的数据处理  
    

    元素的一些属性和方法,当被访问或调用的时候,会触发浏览器的布局和绘制,布局基本上会影响页面的大部分元素,导致耗时长

    为了避免之前提到的布局颠簸,我们需要批量访问和更新DOM。

        var currentTop,
        currentLeft;
    /* 有 layout thrashing. */
    currentTop = element.style.top; /* 访问 */
    element.style.top = currentTop + 1; /* 更新 */
    
    currentLeft = element.style.left; /* 访问 */
    element.style.left = currentLeft + 1; /* 更新 */
    
    /* 没有 layout thrashing. */
    currentTop = element.style.top; /* 访问 */
    currentLeft = element.style.left; /* 访问 */
    
    element.style.top = currentTop + 1; /* 更新 */
    element.style.left = currentLeft + 1; /* 更新 */
    

    计算offset 时,浏览器需要重新计算(重新布局),然后才能返回最新的值

           for(var i = 0; i < list.length; i++) {
      list[i].style.width = parent.offsetWidth + 'px';
    }   
    
var parentWidth = parent.offsetWidth;
for(var i = 0; i < list.length; i++) {
  list[i].style.width = parentWidth + 'px';
}
  • css样式修改
  • 修改布局相关属性,会触发Layout-Paint-Composite
  • 修改绘制相关属性,会触发Paint-Composite
  • 其他属性,某些特别属性可在不同层中单独绘制,在合并图层,就是直接Composite (transform,opacity)

    在绑定 scroll 、resize 这类事件时,当它发生时,它被触发的频次非常高,如果事件中涉及到大量的位置计算、DOM 操作、元素重绘等工作且这些工作无法在下一个 scroll 事件触发前完成,就会造成浏览器掉帧。加之用户鼠标滚动往往是连续的,就会持续触发 scroll 事件导致掉帧扩大、浏览器 CPU 使用率增加、用户体验受到影响。

    类比电梯

    // 防抖动函数
    

    function debounce(func, wait, immediate) {

    var timeout;
    return function() {
        var context = this, args = arguments;
        var later = function() {
            timeout = null;
            if (!immediate) func.apply(context, args);
        };
        var callNow = immediate && !timeout;
        clearTimeout(timeout);
        timeout = setTimeout(later, wait);
        if (callNow) func.apply(context, args);
    };
    

    };

    var myEfficientFn = debounce(function() {

    // 滚动中的真正的操作
    

    }, 250);

    // 绑定监听

    window.addEventListener(‘resize’, myEfficientFn);

       // 简单的节流函数
    function throttle(func, wait, mustRun) {
        var timeout,
            startTime = new Date();
    
        return function() {
            var context = this,
                args = arguments,
                curTime = new Date();
    
            clearTimeout(timeout);
            // 如果达到了规定的触发时间间隔,触发 handler
            if(curTime - startTime >= mustRun){
                func.apply(context,args);
                startTime = curTime;
            // 没达到触发间隔,重新设定定时器
            }else{
                timeout = setTimeout(func, wait);
            }
        };
    

    };
    // 实际想绑定在 scroll 事件上的 handler
    function realFunc(){

    console.log("Success");
    

    }
    // 采用了节流函数
    window.addEventListener(‘scroll’,throttle(realFunc,500,1000));

pointer-events: none 可用来提高滚动时的帧频。的确,当滚动时,鼠标悬停在某些元素上,则触发其上的 hover 效果,然而这些影响通常不被用户注意,并多半导致滚动出现问题。

will-change 为 web 开发者提供了一种告知浏览器该元素会有哪些变化的方法,这样浏览器可以在元素属性真正发生变化之前提前做好对应的优化准备工作。 这种优化可以将一部分复杂的计算工作提前准备好,使页面的反应更为快速灵敏。

类比萨里机长

值得注意的是,用好这个属性并不是很容易:

不要将 will-change 应用到太多元素上:浏览器已经尽力尝试去优化一切可以优化的东西了。有一些更强力的优化,如果与 will-change 结合在一起的话,有可能会消耗很多机器资源,如果过度使用的话,可能导致页面响应缓慢或者消耗非常多的资源。

不要过早应用 will-change 优化:如果你的页面在性能方面没什么问题,则不要添加 will-change 属性来榨取一丁点的速度。 will-change 的设计初衷是作为最后的优化手段,用来尝试解决现有的性能问题。它不应该被用来预防性能问题。过度使用 will-change 会导致大量的内存占用,并会导致更复杂的渲染过程,因为浏览器会试图准备可能存在的变化过程。这会导致更严重的性能问题。

给它足够的工作时间:这个属性是用来让页面开发者告知浏览器哪些属性可能会变化的。然后浏览器可以选择在变化发生前提前去做一些优化工作。所以给浏览器一点时间去真正做这些优化工作是非常重要的。使用时需要尝试去找到一些方法提前一定时间获知元素可能发生的变化,然后为它加上 will-change 属性。

Unable to preventDefault inside passive event listener due to target being treated as passive

window.addEventListener(“touchstart”, func, {passive: true} );

window, document , body