📌 面试官视角
在前端面试中,"如何减少页面重绘跟重排"是一道高频且经典的面试题。这道题不仅考察你对浏览器渲染机制的理解,还能看出你的性能优化意识和实际项目经验。掌握这个知识点,能让你在面试中脱颖而出。
一、面试题分析
❓ 面试题:如何减少页面重绘跟重排?
这道面试题看似简单,实则考察了多个层面的知识:
- 基础概念:什么是重绘?什么是重排?它们有什么区别?
- 触发条件:哪些操作会触发重绘?哪些操作会触发重排?
- 优化策略:如何减少重绘和重排?有哪些具体的优化方法?
- 实践经验:在实际项目中如何应用这些优化策略?
💡 面试加分点
能够从浏览器渲染原理出发,结合具体代码示例,说明优化策略的实际应用,并能分享项目中的实践经验。
二、重绘和重排的概念
1. 什么是重排(Reflow)?
重排,也叫回流,是指浏览器需要重新计算页面元素的几何属性(位置、大小、边距等)的过程。
DOM改变 → 重新计算布局 → 生成渲染树 → 重排2. 什么是重绘(Repaint)?
重绘是指浏览器需要重新绘制页面元素的外观(颜色、背景、边框等)的过程。
样式改变 → 重新绘制 → 重绘3. 重排和重绘的关系
重要结论:重排一定会导致重绘,但重绘不一定导致重排。
| 特性 | 重排(Reflow) | 重绘(Repaint) |
|---|---|---|
| 触发条件 | 元素几何属性改变 | 元素外观属性改变 |
| 性能消耗 | 大(需要重新计算布局) | 小(只需重新绘制) |
| 是否触发对方 | 会触发重绘 | 不一定触发重排 |
| 常见操作 | 改变宽高、位置、边距等 | 改变颜色、背景、边框等 |
三、触发重排和重绘的操作
1. 触发重排的操作
📝 触发重排的常见操作
// 1. 改变元素的几何属性element.style.width='200px';element.style.height='200px';element.style.margin='10px';element.style.padding='10px';element.style.border='1px solid #000';// 2. 改变元素的位置element.style.top='100px';element.style.left='100px';element.style.position='absolute';// 3. 操作DOM树document.body.appendChild(element);element.parentNode.removeChild(element);// 4. 改变字体大小element.style.fontSize='20px';// 5. 获取布局信息(强制同步布局)constwidth=element.offsetWidth;constheight=element.offsetHeight;consttop=element.offsetTop;2. 触发重绘的操作
📝 触发重绘的常见操作
// 1. 改变颜色element.style.color='red';element.style.backgroundColor='blue';element.style.borderColor='green';// 2. 改变背景element.style.background='url(image.jpg)';element.style.backgroundImage='url(image.jpg)';// 3. 改变透明度element.style.opacity='0.5';// 4. 改变可见性element.style.visibility='hidden';// 5. 改变文本样式element.style.textDecoration='underline';element.style.textShadow='2px 2px 2px #000';⚠️ 强制同步布局
在JavaScript中,某些操作会强制浏览器立即执行布局计算,这被称为"强制同步布局"(Forced Synchronous Layout)。这种操作会导致性能问题,应该尽量避免。
四、减少重排和重绘的策略
1. 使用 transform 和 opacity 代替 top/left 和 visibility
❓ 面试题:为什么 transform 和 opacity 性能更好?
transform 和 opacity 不会触发重排,只会触发重绘,而且可以利用 GPU 加速。
📝 性能对比
// ❌ 不推荐:会触发重排element.style.left='100px';element.style.top='100px';element.style.visibility='hidden';// ✅ 推荐:只触发重绘,GPU加速element.style.transform='translate(100px, 100px)';element.style.opacity='0';💡 原理说明
transform 和 opacity 不会改变元素的布局,只是改变元素的绘制方式。浏览器可以将这些操作交给 GPU 处理,从而提高性能。
2. 批量操作 DOM
❓ 面试题:如何批量操作 DOM 以减少重排?
将多次 DOM 操作合并为一次,可以大大减少重排次数。
📝 批量操作 DOM
// ❌ 不推荐:每次操作都会触发重排for(leti=0;i<100;i++){constli=document.createElement('li');li.textContent='Item '+i;document.getElementById('list').appendChild(li);}// ✅ 推荐:使用文档片段批量操作constfragment=document.createDocumentFragment();for(leti=0;i<100;i++){constli=document.createElement('li');li.textContent='Item '+i;fragment.appendChild(li);}document.getElementById('list').appendChild(fragment);3. 避免强制同步布局
❓ 面试题:什么是强制同步布局?如何避免?
强制同步布局是指在 JavaScript 中读取布局信息后立即修改布局,这会强制浏览器立即执行布局计算。
📝 避免强制同步布局
// ❌ 不推荐:强制同步布局functionresizeAll(){constelements=document.querySelectorAll('.item');elements.forEach(element=>{constwidth=element.offsetWidth;// 读取布局element.style.width=width+10+'px';// 立即修改布局});}// ✅ 推荐:分离读取和修改操作functionresizeAll(){constelements=document.querySelectorAll('.item');constwidths=[];elements.forEach(element=>{widths.push(element.offsetWidth);// 先读取所有布局});elements.forEach((element,index)=>{element.style.width=widths[index]+10+'px';// 再修改所有布局});}4. 使用虚拟 DOM
❓ 面试题:虚拟 DOM 如何减少重排和重绘?
虚拟 DOM 通过 Diff 算法计算出最小的变更,然后批量更新真实 DOM,从而减少重排和重绘的次数。
📝 虚拟 DOM 的工作原理
// 虚拟 DOM 的核心思想// 1. 创建虚拟 DOM 树constvirtualDOM={tag:'div',props:{className:'container'},children:[{tag:'h1',props:{},children:['Hello']},{tag:'p',props:{},children:['World']}]};// 2. Diff 算法比较新旧虚拟 DOM// 3. 计算出最小的变更// 4. 批量更新真实 DOM💡 原理说明
虚拟 DOM 的核心思想是:在内存中维护一个虚拟的 DOM 树,当数据变化时,先比较新旧虚拟 DOM 树的差异,然后只将差异部分应用到真实 DOM 上,从而减少重排和重绘的次数。
5. 使用 CSS 动画
❓ 面试题:CSS 动画如何减少重排和重绘?
CSS 动画可以利用 GPU 加速,不会触发重排,只会触发重绘。
📝 CSS 动画示例
/* ❌ 不推荐:使用 JavaScript 动画,会触发重排 */.element{position:absolute;left:0;top:0;}/* ✅ 推荐:使用 CSS 动画,GPU加速 */.element{transform:translateX(0);transition:transform 0.3s ease;}.element:hover{transform:translateX(100px);}/* ✅ 推荐:使用 CSS 动画,GPU加速 */@keyframesslideIn{from{transform:translateX(-100%);}to{transform:translateX(0);}}.element{animation:slideIn 0.3s ease;}💡 原理说明
CSS 动画(特别是使用 transform 和 opacity 的动画)可以利用 GPU 加速,不会触发重排,只会触发重绘,从而提高性能。
6. 使用 will-change 属性
❓ 面试题:will-change 属性如何优化性能?
will-change 属性可以提前告知浏览器哪些属性会发生变化,让浏览器提前做好优化准备。
📝 will-change 属性示例
/* ❌ 不推荐:滥用 will-change */*{will-change:transform,opacity;}/* ✅ 推荐:只在需要时使用 */.element{will-change:transform;}.element:hover{transform:translateX(100px);}/* ✅ 推荐:动画结束后移除 will-change */.element{will-change:transform;animation:slideIn 0.3s ease;}.element.animation-end{will-change:auto;}💡 原理说明
will-change 属性可以提前告知浏览器哪些属性会发生变化,让浏览器提前做好优化准备(如创建新的图层)。但是,滥用 will-change 会导致性能问题,应该只在需要时使用。
7. 使用 requestAnimationFrame
❓ 面试题:requestAnimationFrame 如何优化性能?
requestAnimationFrame 可以根据浏览器的刷新率来执行动画,避免不必要的重绘。
📝 requestAnimationFrame 示例
// ❌ 不推荐:使用 setInterval,可能导致不必要的重绘functionanimate(){element.style.left=parseInt(element.style.left)+1+'px';setInterval(animate,16);}// ✅ 推荐:使用 requestAnimationFrame,根据浏览器刷新率执行functionanimate(){element.style.transform=`translateX(${parseInt(element.style.transform.match(/\d+/)[0])+1}px)`;requestAnimationFrame(animate);}requestAnimationFrame(animate);💡 原理说明
requestAnimationFrame 可以根据浏览器的刷新率(通常是 60Hz)来执行动画,避免不必要的重绘。当页面不可见时,requestAnimationFrame 会自动暂停,从而节省性能。
8. 使用离屏渲染
❓ 面试题:什么是离屏渲染?如何使用?
离屏渲染是指在内存中进行复杂的绘制操作,然后将结果绘制到页面上,从而减少重排和重绘的次数。
📝 离屏渲染示例
// ❌ 不推荐:直接在页面上绘制,会触发多次重绘functiondrawComplexShape(){constcanvas=document.getElementById('canvas');constctx=canvas.getContext('2d');ctx.fillStyle='red';ctx.fillRect(0,0,100,100);ctx.fillStyle='blue';ctx.fillRect(100,0,100,100);ctx.fillStyle='green';ctx.fillRect(0,100,100,100);}// ✅ 推荐:在内存中绘制,然后一次性绘制到页面上functiondrawComplexShape(){constcanvas=document.getElementById('canvas');constctx=canvas.getContext('2d');constoffscreenCanvas=document.createElement('canvas');constoffscreenCtx=offscreenCanvas.getContext('2d');offscreenCtx.fillStyle='red';offscreenCtx.fillRect(0,0,100,100);offscreenCtx.fillStyle='blue';offscreenCtx.fillRect(100,0,100,100);offscreenCtx.fillStyle='green';offscreenCtx.fillRect(0,100,100,100);ctx.drawImage(offscreenCanvas,0,0);}💡 原理说明
离屏渲染是指在内存中进行复杂的绘制操作,然后将结果绘制到页面上,从而减少重排和重绘的次数。这种方法特别适合复杂的动画和图形绘制。
五、实践经验总结
1. 给面试者的建议
🎯 给面试者的建议
- 理解原理:深入理解浏览器渲染机制,知道重排和重绘的区别
- 掌握策略:掌握减少重排和重绘的各种策略
- 实践应用:在实际项目中应用这些优化策略
- 性能测试:使用 Chrome DevTools 进行性能测试和优化
- 持续优化:持续关注性能优化,不断改进代码
- 分享经验:和团队分享性能优化的经验和技巧
- 关注新特性:关注浏览器的新特性和优化方案
2. 面试中的回答策略
🎯 面试中的回答策略
- 先说概念:先解释什么是重排和重绘,以及它们的区别
- 再说触发条件:说明哪些操作会触发重排和重绘
- 然后说优化策略:详细说明减少重排和重绘的各种策略
- 最后说实践经验:分享在实际项目中如何应用这些优化策略
- 结合代码示例:用具体的代码示例说明优化策略的应用
3. 避免的误区
⚠️ 避免的误区
- 过度优化:不要过度优化,要根据实际情况选择合适的优化策略
- 滥用 will-change:不要滥用 will-change 属性,会导致性能问题
- 忽视用户体验:不要为了性能而牺牲用户体验
- 不测试性能:不要凭感觉优化,要用工具测试性能
- 不关注兼容性:不要忽视浏览器兼容性问题
4. 项目中的实际应用
📝 项目中的实际应用
- 列表渲染优化:使用虚拟 DOM 或文档片段批量操作 DOM
- 动画优化:使用 CSS 动画或 requestAnimationFrame
- 图片懒加载:使用 IntersectionObserver API 实现图片懒加载
- 虚拟滚动:对于长列表,使用虚拟滚动技术
- 代码分割:使用 Webpack 的代码分割功能,按需加载
💡 总结
减少页面重绘和重排是前端性能优化的重要手段。通过深入理解浏览器渲染机制,掌握各种优化策略,并在实际项目中应用这些策略,可以大大提高页面的性能。在面试中,能够从原理出发,结合具体代码示例,说明优化策略的实际应用,并能分享项目中的实践经验,会让你在面试中脱颖而出。
will-change 跟 conian 可以参考我之前的文章:
优化重排跟重绘,怎么少的了 will-change
使用CSS Contain 优化你的页面(重排和重绘)
感谢阅读!如果您有任何问题或建议,欢迎在评论区留言讨论。
如果你觉得本文对你有帮助,欢迎点赞、收藏、分享,也欢迎关注我,获取更多前端技术干货!