javascriptiframeadsgoogle-dfpdouble-click-advertising

Google DFP - Resize SafeFrame custom creative outer Iframe container from inside (expand ad)


i'm searching for solution, that can expand SafeFrame custom ad from inside of custom creative in Google DFP, is that possible somehow?


Solution

  • There are 2 possible solutions:

    1) using SafeFrame API

    pros:

    cons:

    2) code your own API with window.postMessage() javascript method

    cons:

    pros:

    1) using SafeFrame API

    This API is realatively easy to use, you can see some examples in GPT Safeframe preview tool.

    First you need to update your DFP initialization script in <head> of your website

    var pageConfig = {
        allowOverlayExpansion: true,
        allowPushExpansion: true,
        sandbox: true
    };
    googletag.pubads().setSafeFrameConfig(pageConfig);
    

    This will allow to expand SafeFrame ads on your website. More about this in Control SafeFrame Container behavior through GPT.

    Now you can create custom creative and serve it as SafeFrame on your website. Here is my one example. This Example can "wait" util it's visible, and then will expand to height of <div id="container"> that is inside of SafeFrame:

    <div id="container">
        some lines to make container height<br>
        line<br>
        line<br>
        line<br>
        line<br>
        line<br>
        line<br>
        line<br>
        line<br>
        line<br>
        line<br>
        line<br>
        line<br>
        line<br>
        line<br>
        line<br>
        line<br>
        line<br>
    </div>
    
    <script>
        // global expanded indicator variable, because API don't have any
        var expanded = false;
        function expand() {
            var self= $sf.ext.geom().self;
            var config = {
                push: true, // we want to push expanded content
                b: 0
            };
    
            var el = document.getElementById('container');
            var containerHeight = el.offsetHeight;
    
            // get height from bottom, that need to be expanded
            var expandBottom = containerHeight - self.h;
    
            // if container is whole inside a SafeFrame, it will not expand
            if(expandBottom < 0) return;
    
            config.b = expandBottom;
            $sf.ext.expand(config);
        }
    
        function expandDelayed(forceExpand) {
            // expand will run just once, or you can force it to run again
            // but collapse first is needed
            if(expanded && forceExpand || !expanded) {
                $sf.ext.collapse();
                expanded = false;
                // there must be some timeout, because .collapse(); method is deplayed somehow
                setTimeout(expand, 0);
            }
        }
    
        $sf.ext.register(160, 150, function(status, data) {
            // this code will do whole magic of "waiting" for right moment
            if (status === 'geom-update') {
                expandDelayed();
            }
    
            // change global expanded status
            if (status === 'expanded') {
                expanded = true;
            }
        });
    
        // init
        expandDelayed();
    </script>
    

    2. code your own API with window.postMessage() javascript method

    First, you need to put this code, to your DFP initialization script in <head> of your website. This code will add an ID of Ad slot as #hash-tag to <iframe>'s src so you can get it from inside of your creative.

    googletag.pubads().addEventListener('slotRenderEnded', function (event) {
        var containerId = event.slot.getSlotElementId();
        var containerEl = document.getElementById(containerId);
    
        if (containerEl === null) return;
    
        var iframeEl = containerEl.querySelectorAll('iframe')[0];
    
        // it's delayed by 10 milliseconds, because iframe is not yet fully rendered
        // and limited to max to 10 seconds to wait 
        var timeoutFunction = function () {
            var src = "#" + containerId;
            // `src` attribute is null, when iframe is FriendlyIframe, and
            // when it's present, then it's SafeFrame
            if (iframeEl.getAttribute('src') !== null) {
                src = iframeEl.getAttribute('src').replace(/#.*/, "") + src;
            } else {
                var name = iframeEl.getAttribute('name') + "#" + containerId;
                iframeEl.setAttribute('name', name);
            }
            iframeEl.setAttribute('src', src);
        };
        setTimeout(timeoutFunction, 10);
    });
    

    Second, you need to add this code to your website, better as separated .js file.

    function onMessageReceivedGetStyle(e) {
    
        // this will filter just setStyle commands from correct origin
        if (
            !(e.origin === 'http://tpc.googlesyndication.com' || e.origin === 'https://tpc.googlesyndication.com') ||
            typeof e.data !== 'object' ||
            typeof e.data.id !== 'string' ||
            e.data.cmd !== 'setStyle' ||
            typeof e.data.params !== 'object'
        ) {
            return;
        }
    
        // remove # character from id, we don't use jquery
        var elementId = e.data.id.replace(/#/, "");
    
        var wrapperEl = document.getElementById(elementId);
        if (wrapperEl === null) {
            return;
        }
    
        var elements = [wrapperEl];
        // you can target child elements too with query parameter
        if (typeof e.data.query === 'string' && e.data.query) {
            elements = wrapperEl.querySelectorAll(e.data.query);
        }
    
        elements.forEach(function (element) {
            Object.keys(e.data.params).forEach(function (param) {
                element.style[param] = e.data.params[param];
            });
        });
    
    }
    
    if (window.addEventListener) {
        addEventListener('message', onMessageReceivedGetStyle, false);
    }
    else {
        if (window.attachEvent) {
            attachEvent('onmessage', onMessageReceivedGetStyle);
        }
        else {
            window.onmessage = onMessageReceivedGetStyle;
        }
    }
    

    And third thing is your custom code in custom type of creative in DFP. Here is example, that is similar to that in first example, but here this script can wait until all content and image is loaded and then will expand/shrink your iframe with creative:

    <div id="container">
        <a href="#" target="_blank">
            <img src="%%FILE:JPG1%%">
        </a>
        <a href="#" target="_blank">
            <img src="%%FILE:JPG2%%">
        </a>
    </div>
    
    <style>
        a {
            display: block;
            margin-bottom: .5em;
        }
        img {
            display: block;
            max-width: 100%;
        }
        *:last-child {
            margin-bottom: 0;
        }
    </style>
    
    <script>
        var container = document.getElementById('container');
    
        function resizeOutsideSafeFrame() {
            if (!window.top.postMessage) {
                return false;
            }
    
            // get ID of your Ad unit <div> with this creative 
            var divGptAdId = '%%PATTERN:url%%';
    
            if (divGptAdId.indexOf('#') !== -1) {
                divGptAdId = divGptAdId.split('#')[1];
            } else {
                divGptAdId = window.location.hash;
            }
    
            if(!divGptAdId) {            
                if (window.name.indexOf('#') !== -1) {               
                    divGptAdId = window.name.split('#')[1];
                }
            }
    
            if(!divGptAdId) {
                return;
            }
    
            // set with to fullwidth, and height to height of container inside creative
            var width = '100%';
            var height = container.offsetHeight + 'px';
    
            // send our request to website
            window.top.postMessage({
                cmd: 'setStyle',
                id: divGptAdId,
                query: 'div, iframe', // we want to target child div and iframe and don't change container styles
                params: {
                    display: 'block',
                    height: height,
                    width: width
                }
            }, '*');
        }
    
        document.onreadystatechange = function () {
            // resize iframe when all is loaded
            if (document.readyState == "complete") {
                resizeOutsideSafeFrame();
            }
        };
    
        // first resize must occur little bit later 
        setTimeout(resizeOutsideSafeFrame, 100);
    </script>
    

    That's all. When you want to change anything on your website from inside of iframe, you can code your own cmd on your website and call this command from inside of the iframe.


    Edit 1: just noticed now, that var divGptAdId = '%%PATTERN:url%%; will not return correct id of div on the page in #hash way, so now it's needed to give him a correct container div id change:

    if(!divGptAdId) {
        return;
    }
    

    to

    if(!divGptAdId) { 
        divGptAdId = 'div-gpt-ad-container-div-id-1';
    }