性能优化之渲染层优化

先有问题再有答案

  1. 浏览器的渲染流程是什么?
  2. 如何理解回流重绘?
  3. 哪些行为会导致强制同步布局?
  4. 渲染任务是什么?
  5. 如何理解浏览器的渲染队列?
  6. 为什么浏览器需要渲染队列?
  7. 渲染优化有哪些方案?
  8. 如下代码渲染多少次?
javascript 复制代码
let app = document.getElementById('app');
app.style.width = app.offsetWidth + 1 + 'px';
app.style.width = 11 + 'px';
app.style.width = app.offsetWidth + 2 + 'px';
app.style.width = 12 + 'px';
app.style.width = app.offsetWidth + 3 + 'px';

渲染流程

浏览器:帧&渲染流程

回流&重绘

回流

回流是指当网页的布局和几何属性发生变化时,浏览器需要重新计算元素的位置和大小,并重新布局页面的过程。

涉及到DOM树的结构变化,改变页面布局。回流会重新计算渲染树中元素的位置和大小,是一种 性能消耗较大 的操作,因为它会触发浏览器重新布局整个页面

例如:

  • 修改DOM元素的位置和大小,例如通过JavaScript修改元素的样式属性、添加或删除元素。
  • 修改浏览器窗口的大小。
  • 修改DOM树的结构,例如添加或删除元素、修改元素的属性。
  • 修改文本内容,例如通过JavaScript修改文本节点的内容或字体大小

重绘

重绘则是指当网页的外观属性(例如颜色、背景、阴影等)发生变化时,浏览器需要重新绘制页面的过程

不涉及DOM树的变化及页面布局变化,只是重新渲染页面样式。重绘的性能开销相对较小,因为它只需要重新绘制元素的外观,而不需要重新计算布局。

例如:

  • 修改CSS样式,例如修改元素的背景颜色、字体颜色等。
  • 使用visibility: hidden隐藏元素,这只会触发重绘而不会触发回流,因为没有布局或位置的变化。

强制同步布局

强制同步布局是指在读取某些布局属性(如offsetWidthoffsetHeight等)时,浏览器会 强制清空渲染队列并立即进行布局计算,以确保返回的值是最新的。这种操作会触发回流,因为浏览器需要重新计算元素的几何属性。

例如:

  • offsetLeft offsetTop offsetWidth offsetHeight offsetParent
  • clientLeft clientTop clientWidth clientHeight
  • scrollTop scrollLeft scrollWidth scrollHeight
  • getClientRects() getBoundingClientRect()

因为浏览器需要确保返回的值是最新的,从而强制清空渲染队列并立即进行布局计算。

渲染任务

渲染任务是指浏览器在渲染页面时需要执行的具体操作。这些任务包括 计算样式、布局、绘制、合成 等步骤。每个渲染任务都是浏览器渲染引擎需要完成的一个独立的工作单元

渲染队列

渲染队列是指浏览器在渲染过程中,将需要执行的渲染任务按照一定顺序排列。渲染队列确保这些任务按照预定义的顺序执行,以保证页面的正确渲染。浏览器会将多个渲染任务放入队列中,并在合适的时机 批量 处理这些任务,以提高渲染效率。

为什么需要渲染队列?

渲染队列可以帮助浏览器将多次样式和布局的修改合并在一起,减少回流和重绘的次数,通过将多个渲染任务放入队列中,可以在合适的时机批量处理这些任务,从而减少频繁的渲染操作,提高渲染效率。

示例分析

demo

javascript 复制代码
let app = document.getElementById('app');
app.style.width = app.offsetWidth + 1 + 'px';
app.style.width = 11 + 'px';
app.style.width = app.offsetWidth + 2 + 'px';
app.style.width = 12 + 'px';
app.style.width = app.offsetWidth + 3 + 'px';

分析如下:

  1. 第一次遇到offsetWidth 触发强制同步布局 清空当前的渲染队列 当前队列为空 不触发回流。
  2. 将app.style.width = app.offsetWidth + 1 + 'px';作为渲染任务加入到渲染队列中。
  3. 将app.style.width = 11 + 'px';作为渲染任务加入到渲染队列中。
  4. 第二次遇到offsetWidth 触发强制同步布局 清空当前的渲染队列 触发回流重绘机制。
  5. 将app.style.width = app.offsetWidth + 2 + 'px';作为渲染任务加入到渲染队列中。
  6. 将app.style.width = 12 + 'px';作为渲染任务加入到渲染队列中。
  7. 第三次遇到offsetWidth 触发强制同步布局 清空当前的渲染队列 触发回流重绘机制。
  8. 将app.style.width = app.offsetWidth + 3 + 'px';作为渲染任务加入到渲染队列中。
  9. 运行结束 渲染队列依然有内容 在渲染时机到来 再次触发回流重绘 重新渲染页面。

所以一共触发了三次回流重绘。

渲染优化

优化的本质:

减少浏览器渲染管道的计算量样式计算、布局、绘制、合成等阶段的耗时),最终目标是保障帧率(如 60fps)

1:降低渲染频率

  • 批处理 DOM 操作:将多次 DOM 更新合并为一次(如虚拟 DOM 的 Diff 算法、框架的批量更新机制)。
  • 避免高频触发回流/重绘 :例如在 resizescroll 等事件中使用防抖/节流。
  • 使用 requestAnimationFrame:将动画逻辑对齐到浏览器的渲染周期,避免无效更新

2:减少渲染范围

  • 通过 will-changetransform 创建独立的合成层,将变化限制在局部(视图分层)。
  • 使用 CSS contain: strict 声明元素的布局/样式独立性,减少影响范围。
  • 按需更新仅更新可视区域内的内容(如虚拟滚动)。
  • 惰性加载非关键资源(图片、组件)。

3:提高渲染效率

  • 优先使用合成器(Compositor)线程 :使用 transformopacity 实现动画,跳过布局和绘制阶段,直接进入合成。
  • 避免强制同步布局(Layout Thrashing) :避免在 JavaScript中交替读写 offsetWidth 等布局属性,导致浏览器被迫立即计算布局
  • 简化选择器复杂度 :减少 CSS 选择器的层级(如避免 div:nth-child(3) > a:hover)。
  • 减少样式冗余:避免频繁修改内联样式,改用 CSS 类切换。
  • 视图分层(分层机制):本质是通过创建合成层,将渲染工作分摊到 GPU,并减少重绘区域。

4:利用硬件加速与并行化

  • GPU 加速 :通过 transform: translateZ(0) 等属性触发 GPU 渲染。
  • Web Workers:将非 UI 计算任务(如数据预处理)移至后台线程,减轻主线程压力。

系列文章

相关推荐
大怪v2 小时前
前端佬们,装起来!给设计模式【祛魅】
前端·javascript·设计模式
sunly_2 小时前
Flutter:页面滚动,导航栏背景颜色过渡动画
开发语言·javascript·flutter
佩奇的技术笔记2 小时前
高性能Java并发编程:线程池与异步编程最佳实践
java·性能优化
vvilkim2 小时前
Vue.js 插槽(Slot)详解:让组件更灵活、更强大
前端·javascript·vue.js
学无止境鸭2 小时前
uniapp报错 Right-hand side of ‘instanceof‘ is not an object
前端·javascript·uni-app
Aphasia3112 小时前
🧑🏻‍💻前端面试高频考题(万字长文📖)
前端·面试
程序饲养员2 小时前
Javascript中export后该不该加default?
前端·javascript·前端框架
一只韩非子3 小时前
一句话告诉你什么叫编程语言自举!
前端·javascript·后端
虾球xz3 小时前
游戏引擎学习第170天
javascript·学习·游戏引擎
拉不动的猪3 小时前
首屏优化资源加载先后顺序---------以及def/async的使用
前端·javascript·面试