首次渲染或js 或 css属性变化会触发浏览器的渲染流程。
- 布局(Layout,初次渲染完成后,再次触发,又叫做reflow,重排或回流)
- 绘制(Paint,初次渲染完成后,再次触发,又叫做重绘)
- 合成(composite)
触发重排的操作:
当元素的大小和位置发生变化时
- window.resize()
- 大小:元素width height属性发生变化
- 元素内容发生变化,比如文字数量,字体大小。因为这些改变会导致元素的大小发生变化。
- 位置:元素position,top right bottom left margin padding发生变化
- 操作style属性
- 用JS查询某些属性值时(offsetHeight、offsetWidth,getComputedStyle):浏览器自身对会发生重排重绘的事件做了优化,当需要重新渲染时,会将这些渲染操作放到渲染队列里,等待合适的时机(requestAnimationFrame,requestIdleCallback),统一执行,从而减少渲染次数。但是,当我们查询某些属性后,立即对其进行写操作(offsetHeight、offsetWidth,getComputedStyle),浏览器为了保证我们查询到的是最新值,就会立即触发渲染。
只触发重绘的操作:
元素的外观发生变化,大小位置不发生变化时
- 颜色,可见性(visibility),背景相关的属性等。
只触发合成的操作:
浏览器在渲染时,生成出多个图层(类似于PS),主线程(也就是渲染线程)将多个图层分别渲染,在合成阶段,将多个图层叠加,生成一帧图片,进行展示
- 使用css属性进行平移,旋转,渐变过渡操作。比如 transform:translate,rotate,等。
- opacity属性
- 使用will-change,将该元素单独提升为一个图层。
优化建议
- CSS属性读写分离。针对触发重排的第6点。不要在两个读之间,进行一次写操作
- 使用虚拟DOM(react,vue)。
- 在进行大量会触发重新渲染的操作时,先将display属性改为none,得到最终样式结果时,再设置为block(等其他属性)。因为display:none的元素,浏览器不会对其进行渲染。
- 使用requestAnimationFrame API,在浏览器渲染下一帧时,统一执行渲染操作。(类似于vue的 nextTick)。
- 对动画使用will-change,将其单独分为一层,这样不会影响其他图层,其他图层也不会触发重新渲染。(position:absolute/fixed 同理。)
- 尽力操作css class 或者 cssText,一次操作多个属性,而不是一条属性一条属性的赋值。