Easy Fix for Cumulative Layout Shift (CLS) in Google AdSense Ads

daily ocr article main image
Date: 2023-12-22 | Category: SEO
Author: Herbert Stonerock

Understanding CLS Issues in AdSense Ads

Many website owners and bloggers using Google AdSense experience a high Cumulative Layout Shift (CLS) score, negatively impacting their site's performance and user experience. This issue occurs when ads are rendered dynamically without reserving the necessary space beforehand, causing content to unexpectedly shift, frustrating users and affecting rankings in Google's Core Web Vitals.

Why CLS Happens and How to Prevent It

When AdSense loads an ad, its script expands the space dynamically, pushing surrounding content downward—resulting in a disruptive layout shift. The solution? Pre-reserving space for the ad before it loads to prevent any unwanted movement. However, pre-reserving space raises a concern: what if no ad loads? This would leave an empty block of space, making your website look awkward and unfinished.

Best Approach: Using Ad Placeholders

Instead of leaving blank space when no ad loads, you can fill it with a visually appealing ad placeholder—such as:

  1. A custom banner promoting your own product or service.
  2. A placeholder graphic ensuring the layout remains consistent.
  3. A message or CTA (Call-To-Action) relevant to your audience.

This method ensures the user experience remains smooth while preventing layout shifts from unfilled ad slots.

Creating Ad Placeholders with HTML & CSS

The first step is to define the size of your ad placeholder, matching Google AdSense’s standard ad dimensions:

  1. 728x90
  2. 336x280
  3. 300x250
  4. 300x50
  5. 160x600

Use this link to access the Google support page for more information.

Example Code for a Simple Ad Placeholder

Here’s a basic CSS and HTML template for a 300×250 ad placeholder:


    .placeholder{
        border: solid 0px transparent;
        border-radius: 5px;
        width: 300px;
        height: 250px;
        background: rgb(84,217,213);
        background: linear-gradient(0deg, rgba(84,217,213,1) 0%, rgba(60,147,207,0.6559873949579832) 100%);
        -webkit-box-shadow: 0px 0px 5px 0px rgba(67,66,69,1);
        -moz-box-shadow: 0px 0px 5px 0px rgba(67,66,69,1);
        box-shadow: 0px 0px 5px 0px rgba(67,66,69,1);      
    }
    .placeholder span{
        line-height:230px;
        text-align: center;
        display: block;
        font-weight: bold;
        font-size: 25px;
        color: white;
        position: relative;
        letter-spacing: 0.02em;
        text-transform: uppercase;
        text-shadow: 0.01em 0 0.15em  rgba(128,128,128,1);
        user-select: none;
        white-space: nowrap;
        filter: blur(0.007em);
    }

<div class="placeholder">
    <span>Ad Placeholder</span>
</div>

The finished placeholder:

Placeholder resulted from code above

This placeholder maintains design consistency while waiting for an ad to be displayed.

Detecting Whether an Ad Has Loaded

To determine if an ad has successfully loaded or not, Google AdSense uses the data-ad-status attribute inside the ins tag:


data-ad-status = "filled" //An ad is loaded
data-ad-status = "unfilled" //No ad is loaded

Using JavaScript's MutationObserver, you can monitor changes to this attribute and automatically swap the placeholder when needed.


<script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-xxxxxxxxxx crossorigin="anonymous"></script>

<ins class="adsbygoogle"
style="display:inline-block;width:300px;height:250px"
data-ad-client="ca-pub-xxxxxxxxxx"
data-ad-slot="1234567890"></ins>
<script>
(adsbygoogle = window.adsbygoogle || []).push({});
</script>

JavaScript Code for Detecting Ads


//in case we have multiple ads to check we will select all ins with the same class
var ins_elements = document.querySelectorAll(".ad-slot-1");



//the callback function which will called when a mutation is occurring
var handleMutation = function(mutations, observer){
    for (var i = 0; i < mutations.length; i++){
        //check if mutation is on data-ad-status
        if ( mutations[i].attributeName === "data-ad-status" ) {
            //check if in is filled or not
            if ( mutations[i].target.dataset.adStatus === "filled" ) {
                //get the placeholder and remove it
                var place_holder = mutations[i].target.querySelector(".placeholder");
                if ( place_holder !== null ) {
                    place_holder.remove();
                }
                //disconnect the observer
                observer.diconnect();
            } else {
                //add the placeholder if necessary
                if ( mutations[i].target.querySelector(".placeholder") === null ) {
                    mutations[i].target.insertAdjacentHTML( 'beforeend', '<div class="placeholder"><span>Ad Placeholder</span></div>');
                }
            }
        }

    }
};


const observer = new MutationObserver(handleMutation);

const config = { attributes: true, childList: true, subtree: true };

//looping through all selected ads
for( var i = 0; i < ins_elements.length; i++ ) {
    observer.observe(ins_elements[i], config);
}

The code you need for a solution to the issue

Bellow you will find the code above stitched up in a sing HTML document, this is just one approach, there are many other ways fix the using the same concept.



<html>
    <head>
        <title></title>
        <style>
            .ad-slot-1{
                width: 300px;
                height: 250px;
                display: block;
                border: solid 1px black;
                cursor: pointer;
                position: relative;
                z-index: 99999;
            }
            
            .placeholder{
                border: solid 0px transparent;
                border-radius: 5px;
                width: 300px;
                height: 250px;
                background: rgb(84,217,213);
                background: linear-gradient(0deg, rgba(84,217,213,1) 0%, rgba(60,147,207,0.6559873949579832) 100%);
                -webkit-box-shadow: 0px 0px 5px 0px rgba(67,66,69,1);
                -moz-box-shadow: 0px 0px 5px 0px rgba(67,66,69,1);
                box-shadow: 0px 0px 5px 0px rgba(67,66,69,1);
                text-decoration: none;
                position: relative;
                z-index: 1;
            }
            .placeholder span{
                line-height:230px;
                text-align: center;
                display: block;
                font-weight: bold;
                font-size: 25px;
                color: white;
                position: relative;
                letter-spacing: 0.02em;
                text-transform: uppercase;
                text-shadow: 0.01em 0 0.15em  rgba(128,128,128,1);
                user-select: none;
                white-space: nowrap;
                filter: blur(0.007em);
            }            
        </style>        
    </head>
    <body>
        <ins id="ad-ins-1" class="ad-slot-1" data-ad-status=""></ins>
        <script>

            //in case we have multiple ads to check we will select all ins with the same class
            var ins_elements = document.querySelectorAll(".ad-slot-1");



            //the callback function which will called when a mutation is occurring
            var handleMutation = function(mutations, observer){
                for (var i = 0; i < mutations.length; i++){
                    console.log("mutation");
                    //check if mutation is on data-ad-status
                    if ( mutations[i].attributeName === "data-ad-status" ) {
                        //check if in is filled or not
                        if ( mutations[i].target.dataset.adStatus === "filled" ) {
                            //get the placeholder and remove it
                            var place_holder = mutations[i].target.querySelector(".placeholder");
                            if ( place_holder !== null ) {
                                place_holder.remove();
                            }
                            //disconnect the observer
                            observer.diconnect();
                        } else {
                            console.log("unfilled");
                            //add the placeholder if necessary
                            if ( mutations[i].target.querySelector(".placeholder") === null ) {
                                mutations[i].target.insertAdjacentHTML( 'beforeend', '<div class="placeholder"><span>Ad Placeholder</span></div>');
                            }
                        }
                    }

                }
            };


            const observer = new MutationObserver(handleMutation);

            const config = { attributes: true, childList: true, subtree: true };

            //looping through all selected ads
            for( var i = 0; i < ins_elements.length; i++ ) {
                observer.observe(ins_elements[i], config);
            }
            
        </script>
                
    </body>
</html>

This script ensures smooth transitions, eliminating empty spaces when ads do not load while maintaining SEO performance and user experience

Fixing CLS issues in Google AdSense ads is essential for boosting Core Web Vitals scores and enhancing the user experience. By pre-reserving ad space and strategically implementing ad placeholders, you maintain a seamless layout—preventing unexpected shifts that could harm engagement and rankings.

Article Contents
Share this article
Subscribe to our blog

Subscribe to the blog newsletter to get updates about articles, tools and services.

Leave a comment


Comments (0)


No comments yet. Be the first to tell us what you think!