阿里云主机折上折
  • 微信号
您当前的位置:网站首页 > 减少重绘与回流的技术

减少重绘与回流的技术

作者:陈川 阅读数:36799人阅读 分类: 性能优化

理解重绘与回流

浏览器渲染页面时,会经历解析HTML构建DOM树、解析CSS构建CSSOM树、合并成渲染树、布局计算、绘制像素等步骤。当DOM元素的外观发生变化但不影响布局时(如修改颜色),触发重绘;当布局属性改变(如宽度、位置),则触发回流,导致重新计算所有受影响元素的几何属性。回流必然引起重绘,但重绘不一定触发回流。

// 触发回流的操作
element.style.width = '200px'; 
// 仅触发重绘的操作 
element.style.color = 'red';

避免频繁操作样式

连续修改样式会导致浏览器多次重排。最佳实践是将多个样式变更合并为一次操作:

// 错误示范 - 多次触发回流
el.style.margin = '5px';
el.style.padding = '10px';
el.style.width = '100px';

// 正确做法 - 使用cssText或class
el.style.cssText = 'margin:5px; padding:10px; width:100px';
// 或
el.classList.add('active-style');

使用文档片段优化DOM操作

直接操作DOM会触发即时回流。DocumentFragment可以作为内存中的DOM节点容器:

const fragment = document.createDocumentFragment();
for(let i=0; i<100; i++){
  const item = document.createElement('div');
  item.textContent = `Item ${i}`;
  fragment.appendChild(item);
}
document.body.appendChild(fragment); // 仅一次回流

批量读取布局属性

强制同步布局(Layout Thrashing)发生在交替读写布局属性时:

// 低效写法 - 导致多次回流
for(let i=0; i<boxes.length; i++) {
  boxes[i].style.width = boxes[i].offsetWidth + 10 + 'px';
}

// 优化方案 - 先读后写
const widths = boxes.map(box => box.offsetWidth);
boxes.forEach((box, i) => {
  box.style.width = widths[i] + 10 + 'px';
});

使用transform和opacity实现动画

CSS3属性不会触发布局计算:

.animate {
  transform: translateX(100px); /* 使用GPU加速 */
  opacity: 0.5; /* 复合层优化 */
  will-change: transform; /* 提前告知浏览器 */
}

优化样式表结构

选择器匹配从右向左进行,避免深层嵌套:

/* 低效选择器 */
div ul li a span.highlight { ... }

/* 优化选择器 */
.highlight { ... }

使用虚拟列表处理长列表

只渲染可视区域内的元素:

function renderVisibleItems(container, items, scrollTop) {
  const itemHeight = 50;
  const startIdx = Math.floor(scrollTop / itemHeight);
  const endIdx = startIdx + Math.ceil(container.clientHeight / itemHeight);
  
  container.innerHTML = '';
  for(let i=startIdx; i<=endIdx; i++) {
    const item = document.createElement('div');
    item.textContent = items[i] || '';
    container.appendChild(item);
  }
}

分离读写操作

利用浏览器的渲染队列机制:

// 触发一次回流
element.style.display = 'none'; // 写
// 中间可以插入其他操作
element.style.width = '100px';  // 写
element.style.display = 'block'; // 写

使用Flexbox/Grid布局

现代布局方式比传统浮动/定位更高效:

.container {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  gap: 16px;
}

合理管理事件监听器

高频事件需要进行节流/防抖:

function debounce(fn, delay) {
  let timer;
  return function() {
    clearTimeout(timer);
    timer = setTimeout(() => fn.apply(this, arguments), delay);
  };
}

window.addEventListener('resize', debounce(handleResize, 100));

使用content-visibility属性

跳过屏幕外元素的渲染:

.long-list-item {
  content-visibility: auto;
  contain-intrinsic-size: 100px 500px;
}

优化图片加载

使用正确的尺寸和格式:

<picture>
  <source srcset="image.webp" type="image/webp">
  <source srcset="image.jpg" type="image/jpeg"> 
  <img src="image.jpg" loading="lazy" decoding="async">
</picture>

减少CSS表达式

避免使用动态计算的CSS值:

/* 避免使用 */
width: expression(document.body.clientWidth > 800 ? "800px" : "auto");

使用requestAnimationFrame

将视觉变化集中在浏览器重绘周期:

function animate() {
  element.style.transform = `translateX(${position}px)`;
  position += 1;
  if(position < 100) {
    requestAnimationFrame(animate);
  }
}
requestAnimationFrame(animate);

离线DOM操作

处理复杂DOM时先使其脱离文档流:

const container = document.getElementById('container');
container.style.display = 'none'; // 触发一次回流
// 执行大量DOM操作
container.style.display = 'block'; // 触发一次回流

避免表格布局

表格会延迟渲染直到所有单元格就绪:

<!-- 避免 -->
<table>
  <tr><td>内容</td></tr>
</table>

<!-- 推荐 -->
<div class="grid-container">
  <div class="grid-item">内容</div>
</div>

使用CSS Containment

限制浏览器重绘/回流范围:

.widget {
  contain: layout paint style;
}

优化字体加载

避免布局偏移和不可见文本闪烁:

@font-face {
  font-family: 'MyFont';
  src: url('myfont.woff2') format('woff2');
  font-display: swap;
}

减少阴影和渐变使用

复杂视觉效果会增加重绘成本:

/* 优化前 */
box-shadow: 0 0 10px rgba(0,0,0,0.5), inset 0 0 5px #fff;

/* 优化后 */
box-shadow: 0 0 5px rgba(0,0,0,0.3);

使用Web Workers处理计算

将耗时任务移出主线程:

// main.js
const worker = new Worker('compute.js');
worker.postMessage(data);
worker.onmessage = (e) => updateUI(e.data);

// compute.js
self.onmessage = (e) => {
  const result = heavyComputation(e.data);
  self.postMessage(result);
};

避免强制同步布局

某些API会强制立即计算布局:

// 触发强制布局
const width = element.offsetWidth; // 读取
element.style.width = width + 10 + 'px'; // 写入

使用CSS变量减少样式变动

通过变量批量更新样式:

:root {
  --theme-color: #4285f4;
}
.button {
  background: var(--theme-color);
}

// JavaScript只需修改一处
document.documentElement.style.setProperty('--theme-color', '#ea4335');

优化第三方脚本加载

使用async/defer属性:

<script src="analytics.js" defer></script>
<script src="widget.js" async></script>

使用Intersection Observer

替代滚动事件监听:

const observer = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    if(entry.isIntersecting) {
      entry.target.classList.add('visible');
    }
  });
});

document.querySelectorAll('.lazy-load').forEach(el => {
  observer.observe(el);
});

避免内联样式

外部样式表更利于浏览器优化:

<!-- 避免 -->
<div style="width:100px; color:red;"></div>

<!-- 推荐 -->
<style>
  .box { width:100px; color:red; }
</style>
<div class="box"></div>

使用CSS will-change

提前告知浏览器可能的变化:

.animated-element {
  will-change: transform, opacity;
}

优化SVG使用

减少SVG复杂度并正确使用:

<svg width="100" height="100">
  <rect x="10" y="10" width="80" height="80" fill="#4285f4"/>
</svg>

减少图层数量

过多的复合层会增加内存消耗:

/* 避免不必要的硬件加速 */
.element {
  transform: translateZ(0); /* 谨慎使用 */
}

使用CSS Content属性

减少额外DOM节点:

.icon-check:before {
  content: "✓";
  color: green;
}

优化滚动性能

启用硬件加速的滚动:

.container {
  overflow-y: scroll;
  scroll-behavior: smooth;
  -webkit-overflow-scrolling: touch;
}

使用媒体查询按需加载

根据设备特性加载资源:

<link rel="stylesheet" href="mobile.css" media="(max-width: 600px)">

避免@import规则

阻塞并行下载:

/* 避免 */
@import url('styles.css');

/* 推荐 */
<link rel="stylesheet" href="styles.css">

使用CSS计数器

替代JavaScript计数:

ol {
  counter-reset: section;
}
li::before {
  counter-increment: section;
  content: counters(section, ".") " ";
}

优化表单元素

减少布局复杂的表单控件:

<input type="range" class="custom-slider">

使用CSS aspect-ratio

避免布局跳动:

.video-container {
  aspect-ratio: 16/9;
}

减少伪元素滥用

复杂的伪元素会增加绘制成本:

/* 适度使用 */
.button::after {
  content: "";
  position: absolute;
  /* 简单效果 */
}

使用CSS revert

重置为浏览器默认样式:

.reset-styles {
  all: revert;
}

优化Web字体使用

选择适当的字体格式和子集:

@font-face {
  font-family: 'Open Sans';
  src: url('OpenSans.woff2') format('woff2');
  unicode-range: U+000-5FF; /* 拉丁字符子集 */
}

使用CSS gap属性

替代margin实现间距:

.grid {
  display: grid;
  gap: 20px;
}

避免频繁的className切换

合并样式变更:

// 低效
element.classList.add('active');
element.classList.remove('inactive');

// 优化
element.className = 'element active';

使用CSS Scroll Snap

实现精准滚动定位:

.container {
  scroll-snap-type: y mandatory;
}
.slide {
  scroll-snap-align: start;
}

优化Canvas绘制

减少canvas状态变更:

// 批量设置状态
ctx.fillStyle = 'red';
ctx.fillRect(10,10,50,50);
ctx.fillRect(70,10,50,50);

// 使用路径批量绘制
ctx.beginPath();
ctx.moveTo(10,10);
ctx.lineTo(50,50);
ctx.lineTo(10,50);
ctx.fill();

使用CSS prefers-reduced-motion

尊重用户运动偏好:

@media (prefers-reduced-motion: reduce) {
  * {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
  }
}

优化阴影效果

使用适当的阴影参数:

/* 优化前 */
box-shadow: 0 0 20px 10px rgba(0,0,0,0.5);

/* 优化后 */
box-shadow: 0 2px 4px rgba(0,0,0,0.1);

使用CSS contain-intrinsic-size

为content-visibility提供占位尺寸:

.lazy-item {
  content-visibility: auto;
  contain-intrinsic-size: 300px 200px;
}

避免不必要的z-index

复杂的层叠上下文会增加计算成本:

/* 避免过度使用 */
.modal {
  z-index: 9999;
}

使用CSS line-clamp

实现多行文本截断:

.ellipsis {
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

优化背景图渲染

避免复杂背景模式:

/* 优化前 */
background: linear-gradient(45deg, #000 25%, transparent 25%) -50px 0,
            linear-gradient(-45deg, #000 25%, transparent 25%) -50px 0;

/* 优化后 */
background: #f5f5f5 url('simple-pattern.png');

使用CSS overscroll-behavior

控制滚动链行为:

.modal-content {
  overscroll-behavior-y: contain;
}

优化CSS变量使用

避免频繁更新CSS变量:

// 低效
element.style.setProperty('--x', x + 'px');
element.style.setProperty('--y', y + 'px');

// 优化
element.style.cssText = `--x:${x}px; --y:${y}px`;

使用CSS revert-layer

回滚到上一层级的样式:

@layer base {
  a { color: blue; }
}
@layer theme {
  a { color: red; }
  .special-link {
    color: revert-layer; /* 回退到base层的蓝色 */
  }
}

优化渐变性能

简化渐变定义:

/* 优化前 */
background: linear-gradient(to right, 
  #ff0000, #ff4000, #ff8000, #ffbf00, #ffff00);

/* 优化后 */
background: linear-gradient(to right, #ff0000, #ffff00);

使用CSS accent-color

快速设置表单控件主题色:

input[type="checkbox"] {
  accent-color: #4285f4;
}

优化滤镜效果

避免复杂滤镜链:

/* 优化前 */
filter: blur(5px) brightness(1.2) contrast(0.8) saturate(1.5);

/* 优化后 */
filter: brightness(1.1);

使用CSS :where()选择器

降低选择器特异性:

/* 高特异性 */
article > .title { color: blue; }

/* 低特异性 */
:where(article > .title) { color: blue; }

优化CSS自定义属性

合理组织变量:

:root {
  --spacing-sm: 8px;
  --spacing-md: 16px;
  --color-primary: #4285f4;
}

.card {
  padding: var(--spacing-md);
  border: 1px solid var(--color-primary);
}

使用CSS :has()选择器

减少JavaScript依赖:

/* 当包含图片时添加边框 */
.card:has(img) {
  border: 1px solid #eee;
}

优化CSS变量计算

避免复杂的calc()运算:

/* 优化前 */
--size: calc(var(--base-size) * 1.5 + 10px);

/* 优化后 */
--size: 25px; /* 预计算值 */

使用CSS :is()选择器

简化选择器组:

/* 简化前 */
.header > h1,
.header > h2,
.header > h3 {
  margin: 0;
}

/* 简化后 */
.header > :is(h1, h2, h3) {
  margin: 0;
}

优化CSS媒体查询顺序

按移动优先原则组织:

/* 基础样式 - 移动设备 */
.container { padding: 10px; }

/* 中等屏幕 */
@media (min-width: 768px) {
  .container { padding: 20px; }
}

/* 大屏幕 */
@media (min-width: 1024px) {
  .container { padding: 30px; }
}

使用CSS :focus-visible

改善键盘导航体验:

button:focus-visible {
  outline: 2px solid blue;
}

优化CSS网格布局

明确轨道定义提高性能:

.grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  grid-auto-rows: minmax(100px, auto);
}

使用CSS :empty伪类

处理空内容状态:

.message:empty::before {
  content: "暂无消息";
  color: gray;
}

优化CSS过渡动画

指定特定属性进行过渡:

/* 优化前 */
transition: all 0.3s ease;

/* 优化后 */
transition: opacity 0.3s ease, transform 0.3s ease;

使用CSS @supports规则

渐进增强样式:

@supports (display: grid) {
  .container {
    display: grid;
  }
}
@supports not (display: grid) {
  .container {
    display: flex;
  }
}

优化CSS计数器使用

减少计数器更新频率:

/* 整个列表使用一个计数器 */
ol {
  counter-reset: item;
}
li {
  counter-increment: item;
}
li::before {
  content: counter(item);
}

使用CSS :target伪类

实现无JS交互效果:

.tab-content {
  display: none;
}
.tab-content:target {
  display: block;
}

优化CSS混合模式

谨慎使用blend-mode:

/* 仅在需要时使用 */
.overlay {
  mix-blend-mode: multiply;
}

使用CSS :invalid伪类

增强表单验证UI:

input:invalid {
  border-color: red;
}

优化CSS视口单位

结合固定值使用:

.header {
  height: calc(100vh - 60px);
}

使用CSS :nth-child()选择器

替代JavaScript循环:

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn

前端川

前端川,陈川的代码茶馆🍵,专治各种不服的Bug退散符💻,日常贩卖秃头警告级的开发心得🛠️,附赠一行代码笑十年的摸鱼宝典🐟,偶尔掉落咖啡杯里泡开的像素级浪漫☕。‌