1.浏览器渲染流程
performance里面的信息
浏览器拿到一串文本,根据''<>''解析出所有标签构造出一颗树,这个就是dom即文档模型,然后会把样式也根据标签加到树里面,即dom树->cssom树
最后dom和cssom两个树合并为render tree,真正需要的渲染树,即比如span为display:none,那么这个render-tree就会把这个元素删掉,而visiblity:hidden还是会绘制的 因为是有dom只是不显示
现在来看渲染路径
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
有这些css动画的 我们可以会为这个元素单独提取一个图层,这样他们改变的时候就不会触发回流重绘,只触发复合,提取效率
如果没设置浏览器会自己来拆分图层 根据元素直接的依赖关系
5.减少重绘
这里点开进行操作会高亮绿色 知道哪些元素进行啦重绘
我们只是设置transform这样还是会引起重绘,因为这个元素没有再单独的图层里,这样还是会引起回流和重绘
我们给属性加上一个willchange:'transform'这样浏览器就知道要提取一个单独图层,这个之前没有单独图层,一般是这种css动画才有必要做单独的图层
6.高频时间处理函数 防抖
高频超过帧数 比如scroll等事件,会在一帧内触发多次,完全没必要处理这莫多次
window.addEventListener('pointermove',(e)=>{
let pos = e.clientX
changeWidth(pos)
console.log('e',e);
})
比如鼠标移动事件,这里一帧会触发n次,下面来看一帧的生命周期
有事件触发视觉变化,一帧就开始啦,在页面布局和绘制之前会触发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时间,