Lazy Loading

Utilizing Lazy Loading to Improve Viewability and CPMs with Audience Network Web Display Bidding

How does Audience Network take Viewability into account when bidding?

  1. Audience Network Terminology:
    • Bid Request: Anytime our system is called for a bid and we reply with a price and a unique bidid.
    • Ad Request: Anytime our system is called for an ad and we reply with the ad assets.
    • Ad Impression: An Audience Network Impression is counted when the user sees the ad on their screen.
    • Show Rate (Viewability): Ad Impressions divided by Ad Requests.
  2. Audience Network takes historical viewability into account when calculating the bid price for a bid request that comes into the system. Here's an example with random publisher metrics:
    • Bid Request: 10,000,000
    • Ad Request: 4,000,000
    • Ad Impression: 2,000,000
    • Show Rate (Viewability): 50%
    • Starting Bid in our Server: $3.00
    • Final Bid to the Client: $1.50
  3. As you can see we deducted 50% off before we responded to the bid request to take into account that the Audience Network ad will only be seen by a user 50% of the time. Lazy loading is a great way to grow your CPMs by increasing your viewability, this is done by holding back on calling our ad to the page if we've won the auction and only delivering it when you believe it will be seen by a user. Read on to learn more about how to utilize Lazy Loading.

What is Lazy Loading?

Lazy loading is a technique used by Publishers to reduce initial page load times and increase the viewability of the advertisements running on their website. This is accomplished by only serving ads when necessary. For example, you wouldn't load BTF ads until the user scrolls down to that area of the page.

Why Lazy Load Ads?

  1. User Experience: Lazy loading removes the majority of your ads from being loaded on page load, this saves on the users CPU and page load speeds.
  2. Advertiser Value: Lazy loading removes the majority of ads on your site that are never seen by a user. The outcome of this is less ad requests, but higher CTR and higher Viewability.

What can a publisher Lazy Load?

  1. Bid Requests (calls to the AN bidding endpoint): Rather then calling for all bids on page load, a Publisher can lazy load the bid requests and call for each bid while the user scrolls down the page (i.e. call for bids for slot 1 on page load, call for bids for the remaining slots when the user is ~500px above the slots location on the page).
  2. Ad Requests (calls to DFP): Similar to bid requests, a Publisher can lazy load the ad requests and call for each ad slot while the user scrolls down the page (i.e. call DFP for slot 1 on page load, call DFP for each remaining slot when the user is ~500px above the slots location on the page).

Lazy Loading Best Practices:

  • Lazy load the bid requests. Audience Network will throttle (not respond to) bid requests if they aren't producing Impressions. Lazy loading the bid requests ensures you're only calling for bids when you know a user will see an ad which greatly lowers the chances for throttling.
  • Lazy load the ad requests. Audience Network uses the ad request metric for viewability so making the ad request only when you know the user will see the ad will maximize your CPM.
  • Test with different trigger distances above the lazy loaded units to maximize viewability. If the trigger is too early the user may not scroll down, if the trigger is too late the user may scroll past before it loads.

How do you Lazy Load with Prebid.js and DFP?

  • Configure your Prebid.js adUnits and bidderSettings like normal.
  • For the requestBids function, only include the adUnits you want called for bids on page load.
  • When Prebid.js triggers the DFP refresh function, only include the DFP slots you want rendered on page load.
  • Set up a scroll event listener to trigger when to begin the ads process for each slot.
  • Use the offSetTop function to find your trigger points above each ad slot.
  • Set up a function to trigger the requestBids function for each remaining ad slot to get triggered by the event listener. These functions should also have their own timeouts and trigger the DFP refresh function once the bid is received.

Example code:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    ...
    ...
    <script>
          var PREBID_TIMEOUT = 700;

          var adUnits = [{
              code: 'div-gpt-ad-1',
              sizes: [[300, 250],
              bids: [{
                  bidder: 'audienceNetwork',
                  params: {
                     placementId: '111111111111111_222222222222222'
                  }
              }]
          },{
              code: 'div-gpt-ad-2',
              sizes: [[300, 250]],
              bids: [{
                  bidder: 'audienceNetwork',
                  params: {
                     placementId: '111111111111111_333333333333333',
                  }
              }]
          }];

          var pbjs = pbjs || {};
          pbjs.que = pbjs.que || [];

        </script>
        <script type="text/javascript" src="prebid.js" async></script>
        <script>
          var googletag = googletag || {};
          googletag.cmd = googletag.cmd || [];

          pbjs.bidderSettings = {
                    standard: {
                        adserverTargeting: [
                            {
                                key: "hb_bidder",
                                val: function (bidResponse) {
                                    return bidResponse.bidderCode;
                                }
                            }, {
                                key: "hb_adid",
                                val: function (bidResponse) {
                                    return bidResponse.adId;
                                }
                            }, {
                                key: "hb_pb",
                                val: function (bidResponse) {
                                    return '20.00';
                                }
                            }]}};

          pbjs.que.push(function() {
              pbjs.addAdUnits(adUnits);
              pbjs.requestBids({
                  adUnitCodes: ['div-gpt-ad-1'],
                  bidsBackHandler: sendAdserverRequest
              });
          });

          function sendAdserverRequest() {
              if (pbjs.adserverRequestSent) return;
              pbjs.adserverRequestSent = true;
              googletag.cmd.push(function() {
                  pbjs.que.push(function() {
                      pbjs.setTargetingForGPTAsync();
                      googletag.pubads().refresh([slot1]);
                  });});};

          setTimeout(function() {
              sendAdserverRequest();
          }, PREBID_TIMEOUT);

        </script>
        <script async='async' src='https://www.googletagservices.com/tag/js/gpt.js'></script>
        <script>
          var slot1, slot2; + googletag.cmd.push(function() {
            slot1 = googletag.defineSlot('/1234567/header_bidding', [300, 250], 'div-gpt-ad-1').addService(googletag.pubads());
            slot2 = googletag.defineSlot('/1234567/header_bidding', [301, 250], 'div-gpt-ad-2').addService(googletag.pubads());
            googletag.pubads().disableInitialLoad();
            googletag.enableServices();
          });
        </script>
  </head>

  <body>


   ....
    Content...
   ....


    <h4>- Slot 1 - 300x250 -</h4>
      <div class="adSlot1" style='height:250px; width:300px;'>
        <div id='div-gpt-ad-1' style='height:250px; width:300px;'>
          <script>
            googletag.cmd.push(function() { googletag.display('div-gpt-ad-1'); });
          </script>
        </div>
      </div>


   ....
    Content...
   ....


    <h4>- Slot 2 - 300x250 -</h4>
      <div class="adSlot2" style='height:250px; width:300px;'>
        <div id='div-gpt-ad-2' style='height:250px; width:300px;'>
          <script>
            googletag.cmd.push(function() { googletag.display('div-gpt-ad-2'); });
          </script>
        </div>
      </div>


   ....
    Content...
   ....


  </body>
  <footer>
    <script>
        var ad2 = document.querySelector(".adSlot2");
        var ad2a = ad2.offsetTop;
        var ad2b = ad2a - 600;
        var slot2Called = false;

        window.addEventListener('scroll', listener);

        var listener = function() {
        if (window.scrollY >= ad2b && !slot2Called) {
          callSlot2();
          slot2Called = true;
          window.removeEventListener('scroll', listener);
        };
      };

        function callSlot2() {
         pbjs.que.push(function() {
           pbjs.requestBids({
             timeout: 500,
             adUnitCodes: ['div-gpt-ad-2'],
             bidsBackHandler: function() {
               pbjs.setTargetingForGPTAsync(['div-gpt-ad-2']);
               googletag.pubads().refresh([slot2]);
             }
           });
         });
       }
    </script>
  </footer>
</html>