浏览器渲染机制
为什么操作DOM慢
- DOM属于渲染引擎中的东西,JS又是JS引擎中的东西,当我们通过JS操作DOM的时候,涉及到了两个线程之间的通信,会带来性能上的损耗
- 操作DOM次数多,等同于一直进行线程之间的通信,操作DOM可能会带来重绘回流的情况,导致性能上的问题
解决方法
- 虚拟滚动(virtualized scroller)
- 原理:只渲染可视区内容,非可视区域完全不渲染,当用户在滚动的时候就实时去替换渲染的内容
什么情况阻塞渲染
- 渲染的嵌套是生成渲染树,所以 HTML 和 CSS 肯定会阻塞渲染。如果你想渲染的越快,你越应该降低一开始需要渲染的文件大小,并且扁平层级,优化选择器。
- 然后当浏览器在解析到
script
标签时,会暂停构建 DOM,完成后才会从暂停的地方重新开始。也就是说,如果你想首屏渲染的越快,就越不应该在首屏就加载 JS 文件,这也是都建议将script
标签放在body
标签底部的原因。 - 当然在当下,并不是说
script
标签必须放在底部,因为你可以给script
标签添加defer
或者async
属性。 - 当
script
标签加上defer
属性以后,表示该 JS 文件会并行下载,但是会放到 HTML 解析完成后顺序执行,所以对于这种情况你可以把script
标签放在任意位置 - 对于没有任何依赖的 JS 文件可以加上
async
属性,表示 JS 文件下载和解析不会阻塞渲染。
重绘(Repaint)和回流(Reflow)
-
重绘是当节点需要更改外观而不会影响布局的,比如改变
color
就叫称为重绘 -
回流是布局或者几何属性需要改变就称为回流。
-
回流必定会发生重绘,重绘不一定会引发回流。回流所需的成本比重绘高的多,改变父节点里的子节点很可能会导致父节点的一系列回流。
-
以下几个动作可能会导致性能问题:
- 改变
window
大小 - 改变字体
- 添加或删除样式
- 文字改变
- 定位或者浮动
- 盒模型
- 改变
-
重绘和回流其实也和 Eventloop 有关。
- 当 Eventloop 执行完 Microtasks 后,会判断
document
是否需要更新,因为浏览器是 60Hz 的刷新率,每 16.6ms 才会更新一次。 - 然后判断是否有
resize
或者scroll
事件,有的话会去触发事件,所以resize
和scroll
事件也是至少 16ms 才会触发一次,并且自带节流功能。 - 判断是否触发了 media query
- 更新动画并且发送事件
- 判断是否有全屏操作事件
- 执行
requestAnimationFrame
回调 - 执行
IntersectionObserver
回调,该方法用于判断元素是否可见,可以用于懒加载上,但是兼容性不好 - 更新界面
- 以上就是一帧中可能会做的事情。如果在一帧中有空闲时间,就会去执行
requestIdleCallback
回调。
- 当 Eventloop 执行完 Microtasks 后,会判断
减少重绘和回流
-
使用
transform
替代top
<div class="test"></div> <style> .test { position: absolute; top: 10px; width: 100px; height: 100px; background: red; } </style> <script> setTimeout(() => { // 引起回流 document.querySelector('.test').style.top = '100px' }, 1000) </script>
使用
visibility
替换display: none
,因为前者只会引起重绘,后者会引发回流(改变了布局) -
不要把节点的属性值放在一个循环里当成循环里的变量
for(let i = 0; i < 1000; i++) { // 获取 offsetTop 会导致回流,因为需要去获取正确的值 console.log(document.querySelector('.test').style.offsetTop) }
-
不要使用
table
布局,可能很小的一个小改动会造成整个table
的重新布局 -
动画实现的速度的选择,动画速度越快,回流次数越多,也可以选择使用
requestAnimationFrame
-
CSS 选择符从右往左匹配查找,避免节点层级过多
-
将频繁重绘或者回流的节点设置为图层,图层能够阻止该节点的渲染行为影响别的节点。比如对于
video
标签来说,浏览器会自动将该节点变为图层。- 设置节点为图层的方式有很多,我们可以通过以下几个常用属性可以生成新图层
will-change
video
、iframe
标签
- 设置节点为图层的方式有很多,我们可以通过以下几个常用属性可以生成新图层