CLS 防治:消除布局偏移以提升用户体验和 SEO

累积布局偏移(CLS) 会让用户感到沮丧,并损害 SEO 排名。本文将介绍系统化的方法来防止布局偏移,并将 CLS 分数控制在 0.1 以下。

理解 CLS 的计算方式

CLS 衡量的是所有意外布局偏移分数的总和。每次偏移分数 = 影响比例 × 距离比例。

预期偏移 vs 意外偏移

由用户触发的偏移(例如点击按钮展开内容)不计入其中。只有以下来源的意外偏移才会被计算:

防止图片布局偏移

始终设置尺寸

<!-- Bad: No dimensions -->
<img src="photo.jpg" alt="Photo">

<!-- Good: Explicit dimensions -->
<img src="photo.jpg" width="800" height="600" alt="Photo">

<!-- Better: Aspect ratio with CSS -->
<img src="photo.jpg" style="aspect-ratio: 4/3; width: 100%;" alt="Photo">

响应式图片容器

.image-container {
  position: relative;
  width: 100%;
  padding-bottom: 56.25%; /* 16:9 aspect ratio */
}
.image-container img {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

防止字体加载导致的 CLS

策略 1:font-display: optional

@font-face {
  font-family: "MyFont";
  src: url("/fonts/myfont.woff2") format("woff2");
  font-display: optional;
}

如果自定义字体未被缓存,则会使用后备字体。这种方式实现零 CLS,但用户在首次访问时可能永远看不到自定义字体。

策略 2:尺寸调整

@font-face {
  font-family: "MyFont";
  src: url("/fonts/myfont.woff2") format("woff2");
  font-display: swap;
  size-adjust: 105.2%; /* Match fallback font metrics */
  ascent-override: 98%;
  descent-override: 25%;
  line-gap-override: 0%;
}

策略 3:字体预加载

<link rel="preload" href="/fonts/myfont.woff2" as="font" type="font/woff2" crossorigin>

防止动态内容导致的 CLS

为延迟加载的内容预留空间

<!-- Reserve space for dynamically loaded content -->
<div style="min-height: 300px;" id="recommendations">
  <!-- Content loads here -->
  <div class="placeholder-skeleton">
    <!-- Skeleton loading state -->
  </div>
</div>

广告位管理

<!-- Reserve fixed space for ads -->
<div class="ad-container" style="min-height: 250px;">
  <div id="ad-slot-1"></div>
</div>

避免在现有内容上方插入内容

// Bad: Pushes content down
container.insertBefore(newElement, container.firstChild);

// Good: Append or replace placeholder
container.appendChild(newElement);
// Or replace a placeholder
placeholder.replaceWith(newElement);

防止第三方组件导致的 CLS

外观(Facade)模式

用轻量级的预览替代笨重的嵌入内容:

<!-- YouTube facade -->
<div class="video-facade" style="aspect-ratio: 16/9;">
  <button class="play-button" onclick="loadVideo()">
    <img src="thumbnail.jpg" width="480" height="270" alt="Video thumbnail">
    <svg class="play-icon">...</svg>
  </button>
</div>

在生产环境中监控 CLS

import {onCLS} from 'web-vitals';

onCLS((metric) => {
  // Log CLS value and shifts
  console.log('CLS:', metric.value);
  metric.entries.forEach((entry) => {
    entry.sources.forEach((source) => {
      console.log('Shift element:', source.node);
    });
  });
});

CLS 优化清单

  1. 为所有图片和视频设置明确的尺寸
  2. 使用 aspect-ratio CSS 属性
  3. 实施 font-display 策略
  4. 为动态内容预留空间
  5. 使用骨架屏加载状态
  6. 预先分配广告位
  7. 为嵌入内容实施外观(facade)模式
  8. 避免在首屏可见区域上方插入内容
  9. 使用 Chrome DevTools 的 Layout Shift Regions 进行测试
  10. 通过 RUM 在生产环境中监控 CLS