Skip to main content
YollaMEDIA

How to Measure the CLS Impact of Your Ad Setup

CLS is the Core Web Vital ads affect most directly. How to measure it, find which ad elements cause layout shifts, and fix the common patterns on gaming sites.

RM

Remel Mendoza

Senior Technical Product Manager · · 14 min read


On this page

Cumulative Layout Shift is the Core Web Vitals metric that ads affect most directly. When an ad loads into a space that was not reserved for it, everything below it moves. Google measures that movement, and CLS is one of the Core Web Vitals it uses in its page experience signals for search.

For gaming publishers, CLS is particularly treacherous because your pages have a fixed focal point — the game area — that users are staring at when the shifts happen. A player watching a game load while a leaderboard ad shoves the game down 90 pixels notices immediately. Even if they do not consciously register the shift, Google's measurement does.

Here is how to measure how much CLS your ad setup causes, which ad elements are the worst offenders, and how to fix the patterns most common on gaming sites.

How CLS Is Calculated

Each individual layout shift gets a score:

Layout Shift Score = Impact Fraction × Distance Fraction

  • Impact Fraction: The share of the viewport covered by the unstable elements (the ones that moved), counting both where they were before the shift and where they ended up
  • Distance Fraction: The largest distance any unstable element moved, divided by the viewport's largest dimension (its width, on a landscape desktop screen)

Shifts are then grouped into session windows: a window keeps growing as long as each new shift arrives less than 1 second after the previous one, up to a maximum of 5 seconds. The scores inside a window add up. A page's CLS is the score of its worst session window, not the total of every shift over the whole visit. Shifts that happen within 500 ms of a user's tap, click, or keypress are excluded.

A page passes with CLS of 0.1 or less.

Example: A 728×90 leaderboard ad loads at the top of a 1366×768 desktop viewport with no reserved space, pushing everything below it down by 90 pixels. The content it pushes spans the full width of the viewport.

  • Impact fraction: the moved content filled the viewport before the shift and still does after it. Impact fraction = 1.0.
  • Distance fraction: content moved 90 px, and the viewport's largest dimension is 1366 px. Distance fraction = 90 / 1366 = 0.066.
  • Layout shift score: 1.0 × 0.066 = 0.066

On its own, that single shift passes, but it has already used two-thirds of the 0.1 budget. Now say an in-content ad loads 0.6 seconds later, without reserved space, and pushes the lower half of the viewport down 250 px:

  • Impact fraction: the moved content covers the bottom half of the viewport before and after the shift. Impact fraction = 0.5.
  • Distance fraction: 250 / 1366 = 0.183.
  • Layout shift score: 0.5 × 0.183 = 0.092

The second shift arrived less than 1 second after the first, so both land in the same session window: 0.066 + 0.092 = 0.157 (0.1574 before rounding). That window fails. If the second ad had loaded 2 seconds later instead, it would have started a new window, and CLS would have been the larger of the two windows, 0.092. That still passes, but only just.

This is why ads loading in a burst right after page load are the classic CLS failure: small shifts that would pass individually add up inside one window.

Measurement Tools

1. Chrome DevTools Performance Panel

This is the most detailed tool for diagnosing CLS causes.

Steps:

  1. Open Chrome DevTools (F12 or Cmd+Option+I)
  2. Navigate to the Performance tab; the live metrics view at the start of the panel shows your current LCP, CLS and INP as you interact with the page
  3. Tick the "Screenshots" checkbox
  4. Click the reload button to record a page load
  5. After the page loads and ads render (wait 10–15 seconds), stop the recording
  6. Look for individual layout shifts in the Layout shifts track
  7. Click each record to see its score and which DOM elements moved, in the Summary pane

What to look for: Each layout shift record shows:

  • The elements that moved (highlighted in the page)
  • The shift score for that individual event
  • The timing relative to page load

For a quick visual check without a recording, open the Rendering tab (from the DevTools command menu) and enable "Layout Shift Regions". Chrome flashes a blue highlight over each area that shifts as the page loads.

On gaming sites, you will often see shifts clustered at two points:

  • 1–3 seconds after load: Initial ad renders (leaderboard, sidebar, in-content units)
  • 5–10 seconds after load: Outstream video expansion, lazy-loaded ads entering the page

Timestamps matter. Shifts less than a second apart add up into one session window, so a cluster of small shifts can fail where each shift alone would pass.

2. Web Vitals Chrome Extension

Install the Web Vitals extension from the Chrome Web Store. It shows LCP, CLS, and INP for the page you are on, and updates as you browse.

For CLS specifically: The extension reports the page's current CLS (its worst session window so far), using the same method as Google's field measurement, and updates it as shifts occur. With console logging turned on in its options, it also logs each shift to the DevTools console with the elements involved. This lets you:

  • Load your gaming site pages and watch CLS change as ads render
  • Identify which moment during the load triggers the shift
  • Compare CLS before and after fixes

Limitation: The extension measures your own visit, on your device and connection. Field data from real users may differ significantly.

3. PageSpeed Insights

Navigate to pagespeed.web.dev and enter your page URL. PageSpeed Insights provides:

  • Lab data: A simulated load using Lighthouse on a throttled connection. It captures shifts during that single load, with no scrolling or interaction.
  • Field data: Real user CLS from the Chrome User Experience Report (CrUX), measured at the 75th percentile over the past 28 days. This is the data Google uses in search.

The critical distinction: Your lab CLS and field CLS can differ dramatically. Lab data covers a single page load. Field data covers the whole time real users spend on the page, including scrolling, ad refreshes, late-loading ads, and user-triggered events, and reports the worst session window from each visit. On ad-supported pages, field CLS is often higher than lab CLS.

Always prioritize field data. If your lab CLS is 0.05 but your field CLS is 0.18, you have a problem that only shows up in real user sessions — likely caused by ad refresh, outstream expansion, or lazy-loaded units that the lab test never scrolls to.

4. Google Search Console Core Web Vitals Report

The CWV report in Search Console groups your URLs into three buckets: Good (CLS ≤ 0.1), Needs Improvement (0.1–0.25), and Poor (> 0.25).

Check this monthly. If URLs are moving from Good to Needs Improvement, something changed: a new ad unit, a layout change, or an SSP script update that altered rendering behavior.

Search Console does not tell you which specific element caused the shift. It tells you which pages are failing, and you use DevTools to diagnose the cause.

5. JavaScript CLS Monitoring

For ongoing monitoring, measure CLS on your own pages with the same session-window logic Google uses. The simplest route is Google's open-source web-vitals library, whose attribution build reports the element responsible for the largest shift. If you want to see the mechanics, here is a minimal version:

let clsValue = 0;         // CLS so far: the worst session window
let clsEntries = [];      // the shifts in that worst window
let sessionValue = 0;     // score of the current session window
let sessionEntries = [];  // shifts in the current session window

const observer = new PerformanceObserver((entryList) => {
  for (const entry of entryList.getEntries()) {
    // Shifts right after user input don't count
    if (entry.hadRecentInput) continue;

    const first = sessionEntries[0];
    const last = sessionEntries[sessionEntries.length - 1];

    // Same window if < 1 s since the last shift and < 5 s since the first
    if (
      last &&
      entry.startTime - last.startTime < 1000 &&
      entry.startTime - first.startTime < 5000
    ) {
      sessionValue += entry.value;
      sessionEntries.push(entry);
    } else {
      sessionValue = entry.value;
      sessionEntries = [entry];
    }

    if (sessionValue > clsValue) {
      clsValue = sessionValue;
      clsEntries = [...sessionEntries];
    }
  }
});

observer.observe({ type: 'layout-shift', buffered: true });

// Report when the page is hidden (tab switch, navigation, close)
document.addEventListener('visibilitychange', () => {
  if (document.visibilityState === 'hidden') {
    const details = clsEntries.map((entry) => ({
      value: entry.value,
      time: Math.round(entry.startTime),
      sources: (entry.sources || []).map((s) => ({
        node: s.node?.nodeName,
        id: s.node?.id,
        class: s.node?.className,
      })),
    }));
    console.log('CLS (worst session window):', clsValue.toFixed(3), details);
    // Send to your analytics endpoint, e.g. with navigator.sendBeacon()
  }
});

This reports CLS the way Google calculates it, plus the elements that moved in the worst window. Deploy it to production to collect field CLS with element-level attribution for your own pages.

Common CLS-Causing Ad Patterns on Gaming Sites

Pattern 1: Top Leaderboard Loading Late

The problem: A 728×90 or 970×90 leaderboard ad at the top of the page loads after the game and the content below it have already rendered. When the ad creative arrives (typically 1–3 seconds after page load), everything below shifts down.

CLS contribution: As the worked example above shows, a 90 px push on a 1366×768 desktop screen scores about 0.066 on its own. A 320×50 mobile banner pushing a full 390×844 phone screen down 50 px scores about 0.059 (50 / 844). Either one uses most of the 0.1 budget, and any other shift within the same second can tip the page over.

The fix: Reserve the dimensions of the leaderboard slot in CSS:

.ad-slot-leaderboard {
  width: 100%;
  max-width: 970px;
  min-height: 90px;
  margin: 0 auto;
}

The min-height: 90px ensures the space exists before the ad loads. When the creative arrives, it fills the already-reserved space, with no shift.

For responsive layouts where the leaderboard may not render on mobile: Use a media query:

@media (min-width: 768px) {
  .ad-slot-leaderboard {
    min-height: 90px;
  }
}
@media (max-width: 767px) {
  .ad-slot-leaderboard {
    min-height: 50px; /* mobile banner: 320×50 */
  }
}

Pattern 2: Sidebar Ads Expanding

The problem: A sidebar ad slot initially renders at 300×250, then an ad creative arrives that is 300×600 (half page). The slot grows by 350 px and pushes everything below it in the sidebar down.

CLS contribution: Depends on how much visible content sits below the slot. With little below it, the shift is small; with a column of content below it, it can be large.

The fix: Design the sidebar to accommodate the largest possible ad size:

.sidebar-ad-container {
  width: 300px;
  min-height: 600px; /* reserve for largest expected creative */
}

If you do not want 600 px of empty space when a 250 px creative loads, be careful about how you give the space back. Shrinking the container moves the content below it up, and that is a layout shift too. The safer options are:

  1. Limit the sizes the slot accepts to the ones that fit the space you reserve.
  2. Reserve the height of the size that wins most often in that slot, and accept an occasional shift when a larger size wins.
  3. Shrink the container only while it is still below the visible part of the page, so nothing on screen moves:
function handleAdLoad(slot, creativeHeight) {
  const container = slot.closest('.sidebar-ad-container');
  const belowViewport =
    container.getBoundingClientRect().top > window.innerHeight;
  if (belowViewport) {
    container.style.minHeight = creativeHeight + 'px';
  }
}

Pattern 3: Outstream Video Expanding In-Content

The problem: An outstream video unit is placed between content sections. When the user scrolls it into view, the unit expands from 0 height to 250–400 px, pushing all content below it down.

CLS contribution: Often the largest single shift on the page, because it moves a lot of content a long way. For example, a player opening to 300 px in the middle of a 1366×768 desktop viewport moves the bottom half of the screen: 0.5 × (300 / 1366) = 0.11, a fail on its own. On a 390×844 phone the same expansion scores 0.5 × (300 / 844) = 0.18.

The fix: Reserve minimum height for the outstream container before the ad loads:

.outstream-container {
  width: 100%;
  min-height: 250px;
  overflow: hidden;
}

If the outstream unit needs to animate open (for user experience reasons), animate inside the reserved space with CSS transforms rather than height changes:

.outstream-container {
  min-height: 250px;
}

.outstream-creative {
  transform: scaleY(0);
  transform-origin: top;
  transition: transform 0.3s ease;
}

.outstream-creative.active {
  transform: scaleY(1);
}

transform animations do not cause layout shifts, because they change how an element is drawn, not where the layout places it. The container space is already reserved; the creative scales into it visually.

Pattern 4: Interstitials That Push or Nudge the Page

The problem: An interstitial is inserted into the document flow instead of overlaying the page, so the page content moves when it appears or disappears. A subtler version: the page hides its scrollbar while the overlay is open, and content shifts sideways by the scrollbar's width when it opens and again when it closes.

CLS contribution: Usually small per occurrence, but the shifts happen at moments when nothing else should be moving.

The fix: Ensure the interstitial uses position: fixed and does not inject elements into the document flow. The overlay should exist on a separate layer:

.interstitial-overlay {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  z-index: 9999;
}

/* Keep space for the scrollbar so locking scroll doesn't shift content */
html {
  scrollbar-gutter: stable;
}

Fixed-position overlays sit outside the document flow, so showing and removing them does not move the page content underneath.

Pattern 5: Ad Slot Collapse When Unfilled

The problem: An ad slot is reserved with min-height in CSS. No ad fills the slot. The empty space is removed (collapsed), pulling content up.

CLS contribution: Depends on the slot size and how much visible content sits below it. Collapsing a 250 px slot in view is comparable to an ad of the same size loading without reserved space.

The fix: Do not collapse unfilled slots while they are in view. Either:

  1. Keep the reserved space (accept the empty area), or
  2. Fill unfilled slots with a house ad or fallback content that matches the reserved dimensions, or
  3. Collapse a slot only while it is still below the visible part of the page, for example a below-the-fold slot that has not been scrolled to yet

Option 2 is the best practice: a house ad promoting another game on your site fills the space, avoids CLS, and serves a business purpose.

Building a CLS Budget

Think of CLS like a budget. Your worst session window has to stay at or below 0.1. Allocate that budget intentionally:

ElementTarget CLS Allocation
Leaderboard ad0.00 (fully reserved)
Sidebar ad0.00 (fully reserved)
In-content display ads0.00 (fully reserved)
Outstream video0.02 (minimized with container reservation)
Ad refresh0.00 (same-size creative replacement)
Non-ad elements (fonts, images, iframes)0.03
Total0.05

A CLS budget of 0.05 gives you 50% headroom below the 0.1 threshold. This buffer accounts for edge cases: slow networks, unusual creative sizes, or third-party scripts that behave unpredictably.

Monitoring After Fixes

After implementing CLS fixes, monitor for regression:

  1. Check field data weekly in PageSpeed Insights for your top 10 pages
  2. Review Search Console CWV report monthly for any URL status changes
  3. Run DevTools checks after every ad configuration change: a new SSP partner, a new ad size, or a new placement can reintroduce CLS
  4. Deploy JavaScript CLS monitoring to production for alerts when CLS exceeds your budget

CLS fixes are not permanent unless you build measurement into your workflow. Ad tech changes frequently: script updates, new creative formats, and SSP rendering changes can reintroduce shifts at any time.

Fix Your CLS

If your gaming site's CLS is above 0.1, your ads are a likely cause. Most of the fixes are CSS-level (reserving dimensions, using fixed positioning, and managing expand and collapse behavior), and none of them require removing ads. For how CLS fits alongside LCP and INP, read Core Web Vitals for gaming publishers, or see publisher results from gaming sites we work with.

Ready to see what managed monetization can do for your gaming site?

Get a Revenue Estimate

Related articles

Core Web Vitals

Lazy Loading Ads: Performance Gains vs Revenue Trade-offs

Lazy loading ads improves page speed and Core Web Vitals, but some impressions never fire. How the trade-off works and how to implement it on gaming sites.

Remel MendozaSenior Technical Product Manager

13 min read