Easy fix Cumulative Layout Shift(CLS) for Google Ads

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

The CLS issue caused by loading ads

Most of the people that run ads from Google AdSense on their websites or blogs experience a high CLS score caused by the ads , this issue occurs when the ads are being rendered and the necessary space for the ad is not reserved. The script that controls the ads will increase the space in order for the ad to fit in it, this will cause all the content below the ad to be pushed down contributing to the CLS with a pretty big layout shift.

One fix would be to reserve the space that the ad requires to be rendered without causing the layout shift, of course the first thought that comes in mind is: What happens when there is no ad loaded by the AdSense script? There will be an empty space with no ad, most likely in the middle of the screen, looking very bad.

The most simple approach to fix the empty space if no ad is loaded is to detect this and place something in that empty space that will both look good and will be of use to you, for example a custom created ad by you promoting one of your products or an ad place holder. Creating your own ads can be a complicated process if you have never done it and since the title of the article says "Easy fix" we will focus on the ad placeholders that can be easily created with a bit of HTML and CSS.

Creating ad placeholders to fix the potentially created layout shift

The first thing you need to know is the size of the placeholder in pixels, the ads have predefined sizes and the placeholder should be the same size, we will chose the 300x250 size for our placeholder but you can chose another size, bellow is a list of the top performing ad sizes and the link to the Google support page that provides more details.

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

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

Below is an example of a basic placeholder written in CSS and HTML.

The CSS:


    .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);
    }

The HTML:


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

The finished placeholder:

Placeholder resulted from code above

Finding out if the ad loaded or not

Now that we got a placeholder we need to detect if the ad is loaded, to be able to detect this we will use the MutationObserver JavaScript interface to detect when the data-ad-status parameter has changed. Based on information from a Google support page the data-ad-status parameter will have the value "filled" if there is an ad and "unfilled" if there is no ad.


data-ad-status = "filled"
data-ad-status = "unfilled"

The data parameter will be set for the ins tag in which the Google ad will be inserted, so wee will need to instruct the MutationObserver to observe this tag and it's parameters, first lets see how the ins tag looks like in plain html.


<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>

Surely you noticed that the adsbygoogle.js script is loaded right before the ins tag starts and that other data parameters are are present, assuming that you already know what these are we will move on to the actual JavaScript code.


//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);
}

This is all you need to fix the CLS issue, of course you can use anything you want to fill up the empty space if the ads script has not provided an ad for your slot, as long as you don't break Google AdSense terms and conditions.

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>

Article Contents
Subscribe to our blog

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

Share this article

Leave a comment


Comments (0)


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