【前端面经】CSS-回流和重绘

文章详细阐述了回流与重绘的概念,回流(布局改变)会引发重绘,而重绘仅改变样式不改位置。浏览器的渲染过程包括DOM和CSSOM树的构建、渲染树生成、回流和重绘。为了避免频繁的回流和重绘影响性能,文章提供了如改变class、使用绝对定位、CSS3硬件加速等优化策略,并举例说明如何更有效地操作DOM以减少渲染开销。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

总结

回流(回流)必将引起重绘, 重绘不一定引起回流(重排)

  • 回流: 会引起元素位置变化的就会reflow(回流),窗口大小改变、字体大小改变、以及元素位置改变,都会引起周围的元素改变他们以前的位置;
  • 重绘: 不会引起位置变化的,只是在以前的位置进行改变背景颜色等,只会repaint(重绘);

一、是什么

在HTML中,每个元素都可以理解成一个盒子,在浏览器解析过程中,会涉及到回流与重绘:

回流:布局引擎会根据各种样式计算每个盒子在页面上的大小与位置

重绘:当计算好盒模型的位置、大小及其他属性后,浏览器根据每个盒子特性进行绘制

具体的浏览器解析渲染机制如下所示:

解析HTML,生成DOM树,解析CSS,生成CSSOM树

将DOM树和CSSOM树结合,生成渲染树(Render Tree)

Layout(回流):根据生成的渲染树,进行回流(Layout),得到节点的几何信息(位置,大小)

Painting(重绘):根据渲染树以及回流得到的几何信息,得到节点的绝对像素

Display:将像素发送给GPU,展示在页面上

在页面初始渲染阶段,回流不可避免的触发,可以理解成页面一开始是空白的元素,后面添加了新的元素使页面布局发生改变

当我们对 DOM 的修改引发了 DOM几何尺寸的变化(比如修改元素的宽、高或隐藏元素等)时,浏览器需要重新计算元素的几何属性,然后再将计算的结果绘制出来

当我们对 DOM的修改导致了样式的变化(color或background-color),却并未影响其几何属性时,浏览器不需重新计算元素的几何属性、直接为该元素绘制新的样式,这里就仅仅触发了重绘

#二、如何触发
要想减少回流和重绘的次数,首先要了解回流和重绘是如何触发的

#回流触发时机
回流这一阶段主要是计算节点的位置和几何信息,那么当页面布局和几何信息发生变化的时候,就需要回流,如下面情况:

添加或删除可见的DOM元素
元素的位置发生变化
元素的尺寸发生变化(包括外边距、内边框、边框大小、高度和宽度等)
内容发生变化,比如文本变化或图片被另一个不同尺寸的图片所替代
页面一开始渲染的时候(这避免不了)
浏览器的窗口尺寸变化(因为回流是根据视口的大小来计算元素的位置和大小的)
还有一些容易被忽略的操作:获取一些特定属性的值

offsetTop、offsetLeft、 offsetWidth、offsetHeight、scrollTop、scrollLeft、scrollWidth、scrollHeight、clientTop、clientLeft、clientWidth、clientHeight

这些属性有一个共性,就是需要通过即时计算得到。因此浏览器为了获取这些值,也会进行回流

除此还包括getComputedStyle方法,原理是一样的

重绘触发时机

触发回流一定会触发重绘

可以把页面理解为一个黑板,黑板上有一朵画好的小花。现在我们要把这朵从左边移到了右边,那我们要先确定好右边的具体位置,画好形状(回流),再画上它原有的颜色(重绘)

除此之外还有一些其他引起重绘行为:

颜色的修改

文本方向的修改

阴影的修改

浏览器优化机制

由于每次重排都会造成额外的计算消耗,因此大多数浏览器都会通过队列化修改并批量执行来优化重排过程。浏览器会将修改操作放入到队列里,直到过了一段时间或者操作达到了一个阈值,才清空队列

当你获取布局信息的操作的时候,会强制队列刷新,包括前面讲到的offsetTop等方法都会返回最新的数据

因此浏览器不得不清空队列,触发回流重绘来返回正确的值

三、如何减少

我们了解了如何触发回流和重绘的场景,下面给出避免回流的经验:

如果想设定元素的样式,通过改变元素的 class 类名 (尽可能在 DOM 树的最里层)
避免设置多项内联样式
应用元素的动画,使用 position 属性的 fixed 值或 absolute 值(如前文示例所提)
避免使用 table 布局,table 中每个元素的大小以及内容的改动,都会导致整个 table 的重新计算
对于那些复杂的动画,对其设置 position: fixed/absolute,尽可能地使元素脱离文档流,从而减少对其他元素的影响
使用css3硬件加速,可以让transform、opacity、filters这些动画不会引起回流重绘
避免使用 CSS 的 JavaScript 表达式
在使用 JavaScript 动态插入多个节点时, 可以使用DocumentFragment. 创建后一次插入. 就能避免多次的渲染性能

但有时候,我们会无可避免地进行回流或者重绘,我们可以更好使用它们

例如,多次修改一个把元素布局的时候,我们很可能会如下操作

const el = document.getElementById('el')
for(let i=0;i<10;i++) {
    el.style.top  = el.offsetTop  + 10 + "px";
    el.style.left = el.offsetLeft + 10 + "px";
}

每次循环都需要获取多次offset属性,比较糟糕,可以使用变量的形式缓存起来,待计算完毕再提交给浏览器发出重计算请求

// 缓存offsetLeft与offsetTop的值
const el = document.getElementById('el')
let offLeft = el.offsetLeft, offTop = el.offsetTop

// 在JS层面进行计算
for(let i=0;i<10;i++) {
  offLeft += 10
  offTop  += 10
}

// 一次性将计算结果应用到DOM上
el.style.left = offLeft + "px"
el.style.top = offTop  + "px"

我们还可避免改变样式,使用类名去合并样式

const container = document.getElementById('container')
container.style.width = '100px'
container.style.height = '200px'
container.style.border = '10px solid red'
container.style.color = 'red'

使用类名去合并样式

<style>
    .basic_style {
        width: 100px;
        height: 200px;
        border: 10px solid red;
        color: red;
    }
</style>
<script>
    const container = document.getElementById('container')
    container.classList.add('basic_style')
</script>

前者每次单独操作,都去触发一次渲染树更改(新浏览器不会),

都去触发一次渲染树更改,从而导致相应的回流与重绘过程

合并之后,等于我们将所有的更改一次性发出

我们还可以通过通过设置元素属性display: none,将其从页面上去掉,然后再进行后续操作,这些后续操作也不会触发回流与重绘,这个过程称为离线操作

const container = document.getElementById('container')
container.style.width = '100px'
container.style.height = '200px'
container.style.border = '10px solid red'
container.style.color = 'red'

离线操作后

let container = document.getElementById('container')
container.style.display = 'none'
container.style.width = '100px'
container.style.height = '200px'
container.style.border = '10px solid red'
container.style.color = 'red'
...(省略了许多类似的后续操作)
container.style.display = 'block'
### TP-Link前端面试经验 #### 面试流程概述 对于TP-Link这样的大型企业,在招聘过程中通常会有严格的筛选机制。应聘者可能会经历多轮面试,包括但不限于简历筛选、在线笔试技术面谈等环节[^2]。 #### 技术考察点 在技术面谈阶段,除了基础的HTML/CSS/JavaScript知识外,还会特别关注候选人的算法与数据结构掌握情况。例如,在一次实际案例中提到的一位求职者被询问到了关于快速排序优化方面的问题,具体涉及三分区法以及随机选取枢纽元这两种改进策略;此外还有并查集这一经典的数据结构也被提及到[^3]。 #### 实际编码能力测试 为了评估候选人解决现实世界编程挑战的能力,面试官往往会布置一些具体的任务让对方当场完成。这不仅考验了其语法熟练度,更要的是解决问题时所展现出的设计思路逻辑思维水平。比如如何处理DOM操作效率低下问题,或是怎样实现响应式布局以适应不同设备屏幕尺寸的需求等等。 #### 深入理解框架原理 现代Web开发离不开各类流行库的支持,因此深入探究这些工具背后的运作机理也是必不可少的一部分。像Vue.js中的`v-on`指令能否绑定多个事件处理器就是一个很好的例子——虽然理论上支持这样做,但在某些特定环境下(如vue-cli构建项目里),如果尝试为同一类型的事件注册超过一个监听函数,则可能导致编译错误发生[^4]。 #### 跨浏览器兼容性解决方案 由于各主流厂商之间存在差异化的渲染引擎版本迭代进度安排,所以编写能够良好运行于所有目标平台上的样式表单变得至关要。针对这一点,掌握了多种CSS Hack技巧无疑会给面试增色不少。例如通过定义带有缀属性的方式区分对待Firefox(-moz-)、Chrome/Safari(-webkit-)、Opera(-o-)乃至Internet Explorer(-ms-)系列的产品特性[^5]。 ```css /* Mozilla内核浏览器:firefox3.5+ */ -moz-transform: rotate(0deg); /* Webkit内核浏览器:Safari and Chrome */ -webkit-transform: rotate(0deg); /* Opera */ -o-transform: rotate(0deg); /* IE9 */ -ms-transform: rotate(0deg); /* W3C标准 */ transform: rotate(0deg); ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

深海大凤梨_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值