本文约 4,082 字,预计阅读 11 分钟。文中命令与配置均经过实测,可直接参考执行。
引言:收益靠广告,体验被广告拖垮
内容站的变现大多依赖展示广告。把广告位塞进正文第二段后面,是最常见的做法,也最容易出事:页面刚渲染时广告位置高度为零,AdSense 的脚本异步加载完成后突然插入一个 250px 高的广告,正文整体下移,读者正在读的那行文字被顶走。CLS 数值轻松突破 0.25,Google Search Console 的核心网页指标报告里,整站 URL 被标成「需要改进」。
更隐蔽的问题是「广告填充率不稳定」。同一位置有时投出 300×250 的方形广告,有时投出 970×90 的横幅,有时干脆不填充。高度不确定意味着你没法用一个固定值兜底,这也是很多人「加了 min-height 还是跳」的原因。本文按广告位的三种形态分别给出处理方案,并给出能真正量化 CLS 的监控代码。
原理分析:广告位偏移的三种成因
| 成因 | 表现 | 解决思路 |
|---|---|---|
| 容器无高度 | 广告到达前高度为 0,到达后撑开 | 用 min-height 或 aspect-ratio 预留 |
| 尺寸不固定 | 不同广告尺寸高度不同 | 固定广告尺寸,或按最高尺寸预留 |
| 脚本异步插入 | 广告节点是 JS 动态创建的 | 先渲染占位骨架,或等容器稳定再插入 |
CLS 的判定有个细节值得利用:hadRecentInput 为 true 的位移不计入 CLS。也就是说,用户点击后 500ms 内的布局变化不算分。但广告通常在用户还没交互时就加载完,所以必须靠预留空间解决,不能指望这个豁免。
另一个常被忽略的点是「字体加载导致的偏移」:如果广告容器里用了自定义字体的「广告」标签,字体 swap 时容器高度会变。这类位移同样计入 CLS,要用 font-display: swap 配合固定行高控制。
需要强调的是,虽然 position: absolute 能把元素移出文档流、彻底消除位移,但广告平台通常不允许这么做(会被判定为隐藏/遮挡内容),不要用这种方式规避。
保姆级实操
- 固定广告尺寸:在广告后台尽量指定单一尺寸(如 300×250),避免自适应尺寸广告带来的高度不确定。
- 容器预留高度:给
.ad-slot设置min-height,值取广告的最大可能高度。 - 响应式用 aspect-ratio:移动端宽度变化时,用
aspect-ratio保持比例,而不是百分比高度。 - 正文插入点包一层:插入广告时不要直接输出
<ins>,先输出带固定高度的<div>容器。 - 骨架屏:容器里放一个灰色占位块,广告到达后填充,视觉上更平滑。
- 监控真实 CLS:用 PerformanceObserver 采集 layout-shift,把数据打到自己能看的地方,别只信实验室数据。
<?php
// 子主题 functions.php:正文第 2 段后插入广告,外层套固定高度容器
add_filter( 'the_content', 'mimangfei_insert_ad_after_paragraph' );
function mimangfei_insert_ad_after_paragraph( $content ) {
if ( ! is_single() || is_feed() ) {
return $content;
}
$ad = '<div class="ad-slot ad-slot--incontent" aria-label="广告">'
. '<div class="ad-slot__placeholder"></div>'
. '<ins class="adsbygoogle" style="display:block" '
. 'data-ad-client="ca-pub-0000000000000000" '
. 'data-ad-slot="1234567890"></ins>'
. '</div>';
$paragraphs = explode( '</p>', $content );
if ( count( $paragraphs ) < 3 ) {
return $content;
}
$paragraphs[1] .= '</p>' . $ad;
return implode( '</p>', $paragraphs );
}
/* 广告位容器:按 300x250 预留,宽度自适应时保持比例 */
.ad-slot--incontent {
position: relative;
width: 100%;
max-width: 300px;
min-height: 250px;
margin: 1.5rem auto;
overflow: hidden;
}
.ad-slot__placeholder {
width: 100%;
height: 250px;
background: #f1f3f5;
border-radius: 4px;
}
/* 桌面端横幅位:用固定高度而非比例 */
@media ( min-width: 1024px ) {
.ad-slot--incontent {
max-width: 728px;
min-height: 90px;
}
.ad-slot__placeholder {
height: 90px;
}
}
// 采集真实用户的布局偏移,便于定位具体元素
new PerformanceObserver( ( list ) => {
for ( const entry of list.getEntries() ) {
if ( entry.hadRecentInput ) {
continue;
}
const sources = entry.sources || [];
for ( const source of sources ) {
const node = source.node;
if ( node && node.className ) {
console.log( 'CLS:', entry.value, node.className );
}
}
}
} ).observe( { type: 'layout-shift', buffered: true } );
常见踩坑
- 只设
min-height不设overflow:广告尺寸更大时会溢出容器,撑开布局。overflow: hidden或固定尺寸都不可少。 - 用百分比高度预留:
height: 20%依赖父元素有确定高度,内容区通常没有,结果等于 0。用min-height的绝对值或aspect-ratio。 - 案例:某站把广告位放在正文第二段后,加了 min-height 但 CLS 仍然是 0.28。查了很久才发现主题给
.entry-content p设了margin-bottom: 1.2em,广告容器插入后把段落间距也算进了位移;更关键的是 AdSense 启用了「自适应广告」,实际投放高度在 90–350px 之间浮动。最后改成固定尺寸 300×250 并把容器max-width锁死,CLS 降到 0.03,广告点击率反而因为位置稳定而略有上升。 - 广告不填充时留大块空白:只做预留不做收起逻辑,未填充时页面出现 250px 空白。可以在广告回调里判断高度,实际为 0 时给容器加
hidden类。 - 异步脚本插入时机太早:
adsbygoogle.push({})在 DOM 未就绪时执行,广告可能插到容器外面。脚本应放在容器之后,或用DOMContentLoaded后再触发。 - 多个广告位同时预留,首屏高度虚高:首屏堆两个预留位,用户要滚动很久才看到正文。首屏最多放一个广告位,其余放在正文中部。
- AMP 与 Web Stories 场景规则不同:AMP 的
amp-ad有独立标签与 layout 属性,别把 Web 端的 CSS 直接套过去。 - 缓存页面导致广告脚本被合并:页面缓存插件把
adsbygoogle.js内联或延迟后,广告可能加载失败,容器一直空着。缓存插件的 JS 延迟队列里要把广告脚本排除。
总结
广告位 CLS 的判断标准是:广告加载前后,容器高度是否恒定。恒定则不产生位移,无论广告是否填充。行动清单:锁定广告尺寸 → 容器设 min-height + overflow: hidden → 响应式用断点切换不同预留值 → 加骨架屏 → 未填充时收起容器 → 用 PerformanceObserver 采集真实数据持续观察。做完这套,变现和体验不必二选一。如果正文里还有大量图片,记得同步按 主题图片懒加载与尺寸属性(width/height) 处理,两处位移叠加才是 CLS 爆表的常见原因。
