Google DFP - Resize SafeFrame custom creative outer Iframe container from inside (expand ad)
Solution 1:
There are 2 possible solutions:
1) using SafeFrame API
pros:
- u can use it "out of the box"
- you can use it on any website, no custom code on website is needed
- it's safe to use
cons:
- it's limited to fill just visible area of website
- need to wait, until ad unit is visible to the user
2) code your own API with window.postMessage()
javascript method
cons:
- you need to add custom code to your website
- it's a possible threat, if you use some 3th party creatives
pros:
- you can do almost anything with your website from your creative
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';
}
Solution 2:
I couldn't find any solid documentation on this so the example from PayteR helped me immensely! I had to play with the new dimension to get the ad to expand the right way.
Here is some sample code I created from PayteR's example:
/* Main object for namespacing */
var Ad = function(obj) {};
/*
Register your ad with the dimensions of the current ad.
- This is basically an event handler for the the ad frame.
- 728 initial ad width
- 90 initial ad height
*/
Ad.prototype.registerExpand = function() {
$sf.ext.register(728, 90, function(status, data){});
};
/* Expand function to be called on click or load */
Ad.prototype.expandAd = function() {
/* Get the current geometry of your ad */
var self = $sf.ext.geom().self;
/*
Set the new geometry
- Increase height 315 pixels
- Expand downward by setting the bottom to the new height minus the current height
*/
$sf.ext.expand({
h: 315,
b: 315 - self.h
});
};
/* Intialize, register, and expand */
var ad = new Ad();
ad.registerExpand();
ad.expandAd();