累积布局偏移(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 优化清单
- 为所有图片和视频设置明确的尺寸
- 使用 aspect-ratio CSS 属性
- 实施 font-display 策略
- 为动态内容预留空间
- 使用骨架屏加载状态
- 预先分配广告位
- 为嵌入内容实施外观(facade)模式
- 避免在首屏可见区域上方插入内容
- 使用 Chrome DevTools 的 Layout Shift Regions 进行测试
- 通过 RUM 在生产环境中监控 CLS