面试必问的渲染流程及优化方案

196 阅读2分钟

首次渲染或js 或 css属性变化会触发浏览器的渲染流程。

  1. 布局(Layout,初次渲染完成后,再次触发,又叫做reflow,重排或回流)
  2. 绘制(Paint,初次渲染完成后,再次触发,又叫做重绘)
  3. 合成(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,将该元素单独提升为一个图层。

优化建议

  1. CSS属性读写分离。针对触发重排的第6点。不要在两个读之间,进行一次写操作
  2. 使用虚拟DOM(react,vue)。
  3. 在进行大量会触发重新渲染的操作时,先将display属性改为none,得到最终样式结果时,再设置为block(等其他属性)。因为display:none的元素,浏览器不会对其进行渲染。
  4. 使用requestAnimationFrame API,在浏览器渲染下一帧时,统一执行渲染操作。(类似于vue的 nextTick)。
  5. 对动画使用will-change,将其单独分为一层,这样不会影响其他图层,其他图层也不会触发重新渲染。(position:absolute/fixed 同理。)
  6. 尽力操作css class 或者 cssText,一次操作多个属性,而不是一条属性一条属性的赋值。