前端性能优化-浏览器渲染篇

569 阅读4分钟

1.浏览器渲染流程

image-20210526114755062

performance里面的信息

image-20210526114832752

浏览器拿到一串文本,根据''<>''解析出所有标签构造出一颗树,这个就是dom即文档模型,然后会把样式也根据标签加到树里面,即dom树->cssom树

image-20210526115113688

image-20210526115236603

最后dom和cssom两个树合并为render tree,真正需要的渲染树,即比如span为display:none,那么这个render-tree就会把这个元素删掉,而visiblity:hidden还是会绘制的 因为是有dom只是不显示

image-20210526115357904

现在来看渲染路径

1.javascript:其实不止是js,代表的是所有的可以引起视觉变化的操作比如js操作dom,或者执行css动画,等

2.style:第一步对视觉有变化啦,浏览器需要重新对样式进行计算,计算出正确的属性是什么样的 这样后面才会有正确的视觉效果

3.layout:布局,即根据我们样式把元素绘制到页面上,但是我们得知道这个元素的位置和大小,这就是布局做的事情(即这个东西多大画在什么地方-几何信息)

4.paint:真正的把东西绘制到页面上

5.composite:复合,我们有多个图层,把不同的东西画在不同层,最后合成一个层

2.布局和绘制

这两步很耗费性能,如果每一次触发视觉变化都走这五个流程,就很慢,浏览器对这两步进行优化,不触发布局和绘制,即一些属性变化不触发这两步

只有触发高度变化,偏移丫这些几何变化位置的才会触发布局这一步(比如herght=xx) 其他的比如颜色变化是不会触发布局的,直接进行重绘

有哪些属性不会触发重绘呢?一些动画可以用gpu就不会引起重绘,直接去复合

影响回流(即布局reflow)的操作(第一次页面渲染叫做布局,后面改变属性引起的一般叫做回流):

1.添加和删除元素(下面的元素位置变化)

2.display:none

3.移动元素的位置

4.offetLeft,left

5.修改浏览器大小和字体大小

6.操作styles

避免布局抖动(layout thrashing),强制回流

解决方法;

避免回流:我们直接修改offestLeft会触发回流,我们可以改成tranform去移动位置,这个动画不会重发回流和重绘,只会触发复合

读写分离:毕竟有时候我们得进行位置操作,我们可以批量进行读,然后再写 不应该读完立马写,这样会强制读取最新的位置触发上一次的回流,本来浏览器会批量更新回流的,如果你这中间读取啦位置,那么浏览器就会强制这个时候就触发回流。

3.fastdom:批量读和写dom

放到封装好的函数里后就能实现先批量读再批量写

4.复合线程和图层

页面拆分为很多图层,当属性变化的时候只会影响某一个图层,方便修改降低影响。

分析元素与元素之间的影响,有影响的提取为单独的一个图层,这样就能一起再一个图层上修改

devtool的show layers去看图层

不进行重绘和回流直接进行图层复合的操作:transfrom 和opacity

image-20210526145914762

有这些css动画的 我们可以会为这个元素单独提取一个图层,这样他们改变的时候就不会触发回流重绘,只触发复合,提取效率

如果没设置浏览器会自己来拆分图层 根据元素直接的依赖关系

5.减少重绘

image-20210526151356226

这里点开进行操作会高亮绿色 知道哪些元素进行啦重绘

我们只是设置transform这样还是会引起重绘,因为这个元素没有再单独的图层里,这样还是会引起回流和重绘

我们给属性加上一个willchange:'transform'这样浏览器就知道要提取一个单独图层,这个之前没有单独图层,一般是这种css动画才有必要做单独的图层

image-20210526152930366

6.高频时间处理函数 防抖

高频超过帧数 比如scroll等事件,会在一帧内触发多次,完全没必要处理这莫多次

window.addEventListener('pointermove',(e)=>{
        let pos = e.clientX
        changeWidth(pos)
        console.log('e',e);
    })

比如鼠标移动事件,这里一帧会触发n次,下面来看一帧的生命周期

image-20210526161947555

有事件触发视觉变化,一帧就开始啦,在页面布局和绘制之前会触发rAf,这样我们利用这个函数先处理我们的函数 再去绘制

fps就是一秒多少帧 比如60fps就是一秒60帧

 window.addEventListener('pointermove',(e)=>{
        let pos = e.clientX
        window.requestAnimationFrame(()=>{
            changeWidth(pos)
        })
       
        console.log('e',e);
    })

这样就加入一帧内触发多次我们也只执行一次 因为requestAnimationFrame就是执行一次

但是我们一帧内一直触发pointermove只执行一次也没意义 这时候我们要防抖 即一帧执行一次pointermove

let ticking = false
    window.addEventListener('pointermove',(e)=>{
        let pos = e.clientX
        if(ticking) return
        ticking = true

        window.requestAnimationFrame(()=>{
            changeWidth(pos)
            ticking=false
        })
       
        console.log('e',e);
    })

只要有requestAnimationFrame在执行 我们就直接return 不去调用requestAnimationFrame

7.react时间调度实现

借助rAf实现requestIdleCallback,ric是在重绘之后,raf是在layout之前 我们根据rAF计算出rIC时间,

image-20210526162834225