JS 入门通关手册(45):浏览器渲染原理与重绘重排(性能优化核心,面试必考

摘要

本文深度拆解浏览器页面渲染的完整流程,从 HTML 解析到最终页面呈现,一步步理清关键渲染路径,详细讲解重排(Reflow)与重绘(Repaint)的核心概念、触发场景、性能影响,总结 10 条实战可用的重排重绘优化方案,结合代码示例与避坑指南,覆盖前端性能优化高频面试考点,帮你从底层理解页面卡顿根源,写出高性能前端代码。

一、前言:为什么要学浏览器渲染原理?

日常开发中,我们经常会遇到页面卡顿、滚动不流畅、交互响应慢等问题,绝大多数情况都和不合理的重排与重绘直接相关。浏览器渲染原理是前端性能优化的核心根基,不仅能帮你从根源解决页面卡顿问题,更是中高级前端面试的必考知识点,无论是校招还是社招,都会围绕重排重绘、渲染优化展开提问。

很多开发者只懂写业务代码,却不懂浏览器如何渲染页面,导致写出的代码看似能运行,却存在严重的性能隐患。掌握这部分内容,能让你跳出 "只会写代码" 的初级阶段,具备性能调优和问题排查的核心能力,也是进阶前端工程师的必备素养。

二、浏览器核心渲染流程(关键渲染路径)

浏览器从接收服务器返回的 HTML、CSS、JS 资源,到最终在屏幕上呈现完整页面,会遵循一套固定的关键渲染路径,总共分为 6 个核心步骤,每一步都环环相扣,缺一不可。

1. 解析 HTML,生成 DOM 树(DOM Tree)

浏览器接收 HTML 文件后,会通过 HTML 解析器对代码进行自上而下的解析,将 HTML 标签转化为浏览器能识别的文档对象模型(DOM),DOM 树完整记录了页面的结构、节点关系以及节点属性,哪怕 HTML 代码有语法错误,浏览器也会自动修正后完成解析。

2. 解析 CSS,生成 CSSOM 树(CSSOM Tree)

与此同时,浏览器会解析 CSS 代码(包括内联样式、内部样式表、外部样式表),生成 CSS 对象模型(CSSOM)。CSSOM 树和 DOM 树结构类似,但它专门记录每个节点对应的样式规则,包含样式的继承关系和优先级,CSSOM 会阻塞渲染,只有全部 CSS 解析完毕,才能进入下一步。

💡 重点注意:CSS 解析会阻塞页面渲染,也会阻塞 JS 执行,但不会阻塞 HTML 解析;如果想加快首屏渲染,可精简 CSS 代码、拆分首屏非必要 CSS。

3. 生成渲染树(Render Tree)

将生成的 DOM 树和 CSSOM 树进行结合,筛选出页面中可见的节点,生成最终的渲染树。渲染树只包含需要显示在页面上的节点及其样式,像 display: none 的节点、head 标签内的元素等不可见节点,不会被加入渲染树;而 visibility: hidden 的节点属于可见节点,会被纳入渲染树。

4. 布局(Layout)/ 重排(Reflow)

根据渲染树,浏览器开始计算每个可见节点在视口中的精确位置和尺寸大小,这个过程叫做布局,也叫重排。这一步会确定每个元素在页面上的坐标、占据空间,是渲染流程中最耗时的环节之一。

5. 分层(Layer)

为了提升渲染效率,浏览器会把渲染树分成多个独立的图层,各自进行绘制,最后再合成一个页面。常见的会触发分层的属性有 transform、opacity、z-index、filter 等,分层后修改对应样式,不会触发重排和重绘,直接进入合成阶段,这也是性能优化的关键突破口。

6. 绘制(Paint)/ 重绘(Repaint)与合成(Composite)

绘制阶段:浏览器将每个图层的像素内容绘制出来,填充颜色、图片、文字、阴影等视觉样式,这个过程就是重绘。合成阶段:将所有绘制好的图层,按照正确的层级顺序合成到一起,最终呈现在屏幕上。这一步效率最高,也是我们做性能优化时最希望触发的阶段。

完整渲染流程总结

HTML 解析 → 生成 DOM 树 → CSS 解析 → 生成 CSSOM 树 → 结合生成渲染树 → 布局(重排)→ 分层 → 绘制(重绘)→ 图层合成

三、重排(Reflow)与重绘(Repaint)核心概念

1. 重排(Reflow / 回流)

定义:当页面中元素的尺寸、位置、结构发生改变,或者浏览器窗口大小变化时,浏览器会重新计算元素的几何属性,重新生成渲染树并执行布局流程,这个过程就是重排。

核心特点:重排是最消耗性能的操作,一旦触发重排,必然会触发后续的重绘和图层合成;重排的范围越大,性能损耗越严重(比如修改根节点,会触发整个页面重排)。

常见触发重排的操作(必记)
  • 修改元素几何属性:宽高(width/height)、内外边距(padding/margin)、边框(border)、定位(top/left/position)
  • 添加 / 删除可见 DOM 元素,改变元素显示隐藏(display: none/block)
  • 浏览器窗口大小改变(resize 事件)
  • 获取元素位置 / 尺寸属性(offsetWidth、offsetHeight、clientWidth、scrollTop 等)
  • 修改页面字体大小、文字内容

2. 重绘(Repaint)

定义:元素的外观、样式发生改变,但几何属性和位置没有变化,浏览器只需重新绘制元素的视觉样式,无需重新计算布局,这个过程就是重绘。

核心特点:重绘性能损耗远低于重排,重绘不一定会触发重排,但重排一定会触发重绘。

常见触发重绘的操作(必记)
  • 修改颜色样式:color、background-color、border-color
  • 修改文字样式:text-decoration、font-style
  • 修改阴影、透明度(opacity 不触发重排重绘,直接合成)
  • visibility 属性切换

3. 图层合成(Composite)

修改 transform、opacity 属性时,浏览器会直接进行图层合成,既不触发重排,也不触发重绘,是性能最优的修改方式,这也是前端动画优化的核心方案。

⚠️ 避坑提醒:opacity 必须单独分层才会直接合成,若元素没有独立图层,修改 opacity 仍会触发重绘;尽量配合 transform 使用,确保分层生效。

四、重排重绘性能优化方案(实战 10 条)

优化核心原则:减少重排次数,缩小重排范围,尽量触发重绘,优先触发图层合成,以下是开发中可直接落地的优化方案,每条都附带代码示例。

1. 批量修改 DOM 样式,避免逐条修改

逐条修改样式会多次触发重排,建议通过修改类名或者 cssText,实现一次性样式修改,仅触发一次重排。

javascript

运行

javascript 复制代码
// 不推荐:多次修改,触发多次重排
const box = document.querySelector('.box')
box.style.width = '200px'
box.style.height = '200px'
box.style.margin = '10px'

// 推荐:批量修改,仅触发一次重排
// 方式1:修改类名
box.classList.add('active')

// 方式2:cssText一次性修改
box.style.cssText = 'width:200px;height:200px;margin:10px'

2. 离线操作 DOM,减少页面重排

对 DOM 进行多次操作时,先将元素脱离文档流,操作完成后再放回文档,全程只触发两次重排(脱离和放回),大幅减少重排次数。

javascript

运行

javascript 复制代码
// 推荐:使用文档碎片离线操作DOM
const list = document.querySelector('.list')
const fragment = document.createDocumentFragment()

for(let i = 0; i< 10; i++){
  const li = document.createElement('li')
  li.textContent = `列表项${i}`
  // 先添加到文档碎片,不触发重排
  fragment.appendChild(li)
}

// 一次性添加到页面,仅触发一次重排
list.appendChild(fragment)

3. 避免频繁获取元素几何属性

浏览器会维护重排队列,频繁获取 offsetWidth、clientTop 等属性,会强制浏览器清空队列,立即执行重排,建议将属性值缓存起来,避免重复获取。

javascript

运行

javascript 复制代码
// 不推荐:频繁获取,触发多次重排
for(let i = 0; i< 10; i++){
  box.style.top = `${box.offsetTop + 10}px`
}

// 推荐:缓存属性值,仅获取一次
let top = box.offsetTop
for(let i = 0;< 10; i++){
  top += 10
  box.style.top = `${top}px`
}

4. 使用 transform 做动画,替代 top/left

CSS 动画尽量用 transform 和 opacity,直接触发图层合成,无重排重绘,性能远超修改 top、left、margin 等属性。

css

css 复制代码
/* 不推荐:触发重排+重绘,动画卡顿 */
.box {
  transition: all 0.3s;
  position: absolute;
  left: 0;
}
.box:hover { left: 50px; }

/* 推荐:仅图层合成,动画流畅 */
.box {
  transition: all 0.3s;
}
.box:hover { transform: translateX(50px); }

5. 固定定位 / 绝对定位,脱离文档流

将需要频繁修改的元素设置为 fixed 或 absolute 定位,使其脱离文档流,修改该元素样式时,只会触发自身重排,不会影响其他元素,缩小重排范围。

6. 避免使用 table 布局

table 布局的重排成本极高,table 内部一个小元素发生变化,会触发整个 table 的重新布局,日常布局优先使用 flex、grid 布局,性能更优。

7. 开启 GPU 硬件加速

通过 transform: translateZ (0) 或者 will-change 属性,为元素开启独立图层,触发 GPU 硬件加速,提升渲染性能,注意不要滥用,过多图层会占用内存。

css

css 复制代码
/* 开启硬件加速,提前告知浏览器元素会变化 */
.box {
  will-change: transform;
  transform: translateZ(0);
}

8. 减少 CSS 表达式和昂贵样式

避免使用 CSS 表达式(expression)、复杂的 CSS 选择器、大量阴影和渐变,这些样式会增加渲染树生成和绘制的耗时,影响渲染效率。

9. 图片提前设置宽高

图片加载完成后,浏览器会重新计算布局,提前给 img 标签设置 width 和 height,避免图片加载时触发页面重排,防止页面抖动。

10. 防抖节流优化 resize/scroll 事件

窗口 resize、页面 scroll 事件会频繁触发重排,通过防抖(debounce)和节流(throttle)控制触发频率,大幅减少重排次数,优化页面流畅度。

五、高频面试题(必背标准答案)

  1. **浏览器渲染流程是什么?**答:HTML 解析生成 DOM 树,CSS 解析生成 CSSOM 树,两者结合生成渲染树,然后执行布局(重排)、分层、绘制(重绘),最后图层合成呈现页面。

  2. **重排和重绘的区别是什么?哪个性能更差?**答:重排是元素尺寸位置变化,重新计算布局;重绘是样式变化,无需计算布局。重排性能损耗远大于重绘,且重排必然触发重绘。

  3. **哪些操作会触发重排?如何避免?**答:修改宽高、位置、增删 DOM、窗口缩放等会触发重排;通过批量修改样式、离线操作 DOM、用 transform 做动画等方式避免。

  4. **CSS 动画用什么属性性能最好?为什么?**答:transform 和 opacity 性能最好,修改这两个属性会直接触发图层合成,既不触发重排,也不触发重绘。

  5. **display: none 和 visibility: hidden 的区别,对重排重绘有什么影响?**答:display: none 会移除元素,触发重排 + 重绘;visibility: hidden 只是隐藏元素,元素仍占空间,仅触发重绘。

六、总结(核心要点速记)

  1. 浏览器渲染核心流程:DOM 树→CSSOM 树→渲染树→重排→重绘→合成,优先级依次降低。
  2. 重排损耗性能 > 重绘 > 图层合成,优化核心是减少重排、避免频繁重排。
  3. 动画优先用 transform 和 opacity,批量操作 DOM,缓存几何属性,是最实用的优化手段。
  4. 重排一定会触发重绘,重绘不一定触发重排,图层合成是性能最优解。
  5. 这部分知识是前端性能优化的核心,也是面试高频考点,务必牢记触发场景和优化方案。
相关推荐
大家的林语冰3 小时前
《前端周刊》尤大开源 Vite+ 全家桶,前端工业革命启动;尤大爆料 Void 云服务新产品,Vite 进军全栈开发;ECMA 源码映射规范......
前端·javascript·vue.js
jiayong234 小时前
第 8 课:开始引入组合式函数
前端·javascript·学习
天若有情6734 小时前
【C++原创开源】formort.h:一行头文件,实现比JS模板字符串更爽的链式拼接+响应式变量
开发语言·javascript·c++·git·github·开源项目·模版字符串
yuki_uix5 小时前
重排、重绘与合成——浏览器渲染性能的底层逻辑
前端·javascript·面试
止观止5 小时前
拥抱 ESNext:从 TC39 提案到生产环境中的现代 JS
开发语言·javascript·ecmascript·esnext
时寒的笔记6 小时前
js逆向7_案例惠nong网
android·开发语言·javascript
吴声子夜歌6 小时前
ES6——Generator函数详解
前端·javascript·es6
吴声子夜歌6 小时前
ES6——Set和Map详解
前端·javascript·es6
xinzheng新政7 小时前
Javascript 深入学习基础·4
javascript·学习·servlet