-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Emits a BID_VIEWABLE event for banner ads when a bid meets IAB viewable specifications, using the browsers IntersectionObserver API, if it is available - adds the new module, markdown documentation, an integration example, and tests
- Loading branch information
Showing
4 changed files
with
411 additions
and
0 deletions.
There are no files selected for viewing
136 changes: 136 additions & 0 deletions
136
integrationExamples/postbid/bidViewabilityIO_example.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,136 @@ | ||
<html> | ||
<head> | ||
<script> | ||
var pbjs = pbjs || {}; | ||
pbjs.que = pbjs.que || []; | ||
|
||
(function() { | ||
var pbjsEl = document.createElement("script"); | ||
pbjsEl.type = "text/javascript"; | ||
pbjsEl.async = true; | ||
pbjsEl.src = '../../build/dev/prebid.js'; | ||
var pbjsTargetEl = document.getElementsByTagName("head")[0]; | ||
pbjsTargetEl.insertBefore(pbjsEl, pbjsTargetEl.firstChild); | ||
})(); | ||
|
||
pbjs.que.push(function() { | ||
var adUnits = [ | ||
{ | ||
code: 'regular_iframe', | ||
mediaTypes: { | ||
banner: { | ||
sizes: [[300, 250]] | ||
} | ||
}, | ||
bids: [ | ||
{ | ||
bidder: 'appnexus', | ||
params: { | ||
placementId: 13144370 | ||
} | ||
} | ||
] | ||
}, | ||
{ | ||
code: 'large_iframe', | ||
mediaTypes: { | ||
banner: { | ||
sizes: [[970, 250]] | ||
} | ||
}, | ||
bids: [ | ||
{ | ||
bidder: 'appnexus', | ||
params: { | ||
placementId: 13144370 | ||
} | ||
} | ||
] | ||
}, | ||
]; | ||
|
||
pbjs.setConfig({ | ||
bidderTimeout: 1000, | ||
bidViewabilityIO: { | ||
enabled: true, | ||
} | ||
}); | ||
|
||
pbjs.onEvent('adRenderSucceeded', ({bid}) => { | ||
var p = document.createElement('p'); | ||
p.innerHTML = bid.adUnitCode + ' was rendered'; | ||
document.getElementById('notes').appendChild(p); | ||
}); | ||
|
||
pbjs.onEvent('bidViewable', (bid) => { | ||
var p = document.createElement('p'); | ||
p.innerHTML = bid.adUnitCode + ' was viewed'; | ||
document.getElementById('notes').appendChild(p); | ||
}); | ||
|
||
pbjs.addAdUnits(adUnits); | ||
|
||
pbjs.requestBids({ | ||
bidsBackHandler: function(bidResponses) { | ||
Object.keys(bidResponses).forEach(adUnitCode => { | ||
var highestCpmBids = pbjs.getHighestCpmBids(adUnitCode); | ||
var winner = highestCpmBids.pop(); | ||
if (winner && winner.mediaType === 'banner') { | ||
var iframe = document.getElementById(adUnitCode); | ||
var iframeDoc = iframe.contentWindow.document; | ||
pbjs.renderAd(iframeDoc, winner.adId); | ||
} else if (winner) { | ||
iframe.width = 300; | ||
iframe.height = 300; | ||
iframeDoc.write('<head></head><body>unsupported mediaType</body>'); | ||
iframeDoc.close(); | ||
} else { | ||
iframe.width = 300; | ||
iframe.height = 300; | ||
iframeDoc.write('<head></head><body>no winner</body>'); | ||
iframeDoc.close(); | ||
} | ||
}); | ||
} | ||
}) | ||
}); | ||
|
||
</script> | ||
|
||
</head> | ||
|
||
<body> | ||
<div id="notes" style="position: fixed; right: 0; width: 50%; height: 100%;"></div> | ||
|
||
<div style="height: 100%"></div> | ||
|
||
<iframe id='regular_iframe' | ||
FRAMEBORDER="0" | ||
SCROLLING="no" | ||
MARGINHEIGHT="0" | ||
MARGINWIDTH="0" | ||
TOPMARGIN="0" | ||
LEFTMARGIN="0" | ||
ALLOWTRANSPARENCY="true" | ||
WIDTH="0" | ||
HEIGHT="0"> | ||
</iframe> | ||
|
||
<div style="height: 100%"></div> | ||
|
||
<iframe id='large_iframe' | ||
FRAMEBORDER="0" | ||
SCROLLING="no" | ||
MARGINHEIGHT="0" | ||
MARGINWIDTH="0" | ||
TOPMARGIN="0" | ||
LEFTMARGIN="0" | ||
ALLOWTRANSPARENCY="true" | ||
WIDTH="0" | ||
HEIGHT="0"> | ||
</iframe> | ||
|
||
<div style="height: 100%"></div> | ||
|
||
</body> | ||
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
import { config } from '../src/config.js'; | ||
import * as events from '../src/events.js'; | ||
import { EVENTS } from '../src/constants.json'; | ||
import { logMessage } from '../src/utils.js'; | ||
|
||
const MODULE_NAME = 'bidViewabilityIO'; | ||
const CONFIG_ENABLED = 'enabled'; | ||
|
||
// IAB numbers from: https://support.google.com/admanager/answer/4524488?hl=en | ||
const IAB_VIEWABLE_DISPLAY_TIME = 1000; | ||
const IAB_VIEWABLE_DISPLAY_LARGE_PX = 242000; | ||
export const IAB_VIEWABLE_DISPLAY_THRESHOLD = 0.5 | ||
export const IAB_VIEWABLE_DISPLAY_LARGE_THRESHOLD = 0.3; | ||
|
||
const CLIENT_SUPPORTS_IO = window.IntersectionObserver && window.IntersectionObserverEntry && window.IntersectionObserverEntry.prototype && | ||
'intersectionRatio' in window.IntersectionObserverEntry.prototype; | ||
|
||
const supportedMediaTypes = [ | ||
'banner' | ||
]; | ||
|
||
export let isSupportedMediaType = (bid) => { | ||
return supportedMediaTypes.includes(bid.mediaType); | ||
} | ||
|
||
// returns options for the iO that detects if the ad is viewable | ||
export let getViewableOptions = (bid) => { | ||
if (bid.mediaType === 'banner') { | ||
return { | ||
root: null, | ||
rootMargin: '0px', | ||
threshold: bid.width * bid.height > IAB_VIEWABLE_DISPLAY_LARGE_PX ? IAB_VIEWABLE_DISPLAY_LARGE_THRESHOLD : IAB_VIEWABLE_DISPLAY_THRESHOLD | ||
} | ||
} | ||
} | ||
|
||
// markViewed returns a function what will be executed when an ad satisifes the viewable iO | ||
export let markViewed = (bid, entry, observer) => { | ||
return () => { | ||
observer.unobserve(entry.target); | ||
events.emit(EVENTS.BID_VIEWABLE, bid); | ||
logMessage(`id: ${entry.target.getAttribute('id')} code: ${bid.adUnitCode} was viewed`); | ||
} | ||
} | ||
|
||
// viewCallbackFactory creates the callback used by the viewable IntersectionObserver. | ||
// When an ad comes into view, it sets a timeout for a function to be executed | ||
// when that ad would be considered viewed per the IAB specs. The bid that was rendered | ||
// is passed into the factory, so it can pass it into markViewed, so that it can be included | ||
// in the BID_VIEWABLE event data. If the ad leaves view before the timer goes off, the setTimeout | ||
// is cancelled, an the bid will not be marked as viewed. There's probably some kind of race-ish | ||
// thing going on between IO and setTimeout but this isn't going to be perfect, it's just going to | ||
// be pretty good. | ||
export let viewCallbackFactory = (bid) => { | ||
return (entries, observer) => { | ||
entries.forEach(entry => { | ||
if (entry.isIntersecting) { | ||
logMessage(`viewable timer starting for id: ${entry.target.getAttribute('id')} code: ${bid.adUnitCode}`); | ||
entry.target.view_tracker = setTimeout(markViewed(bid, entry, observer), IAB_VIEWABLE_DISPLAY_TIME); | ||
} else { | ||
logMessage(`id: ${entry.target.getAttribute('id')} code: ${bid.adUnitCode} is out of view`); | ||
if (entry.target.view_tracker) { | ||
clearTimeout(entry.target.view_tracker); | ||
logMessage(`viewable timer stopped for id: ${entry.target.getAttribute('id')} code: ${bid.adUnitCode}`); | ||
} | ||
} | ||
}); | ||
}; | ||
}; | ||
|
||
export let init = () => { | ||
events.on(EVENTS.AD_RENDER_SUCCEEDED, ({doc, bid, id}) => { | ||
// read the config for the module | ||
const globalModuleConfig = config.getConfig(MODULE_NAME) || {}; | ||
// do nothing if module-config.enabled is not set to true | ||
// this way we are adding a way for bidders to know (using pbjs.getConfig('bidViewability').enabled === true) whether this module is added in build and is enabled | ||
if (globalModuleConfig[CONFIG_ENABLED] && CLIENT_SUPPORTS_IO && isSupportedMediaType(bid)) { | ||
let viewable = new IntersectionObserver(viewCallbackFactory(bid), getViewableOptions(bid)); | ||
let element = document.getElementById(bid.adUnitCode); | ||
viewable.observe(element); | ||
} | ||
}); | ||
} | ||
|
||
init() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
# Overview | ||
|
||
Module Name: bidViewabilityIO | ||
|
||
Purpose: Emit a BID_VIEWABLE event when a bid becomes viewable using the browsers IntersectionObserver API | ||
|
||
Maintainer: [email protected] | ||
|
||
# Description | ||
- This module will trigger a BID_VIEWABLE event which other modules, adapters or publisher code can use to get a sense of viewability | ||
- You can check if this module is part of the final build and whether it is enabled or not by accessing ```pbjs.getConfig('bidViewabilityIO')``` | ||
- Viewability, as measured by this module is not perfect, nor should it be expected to be. | ||
- The module does not require any specific ad server, or an adserver at all. | ||
|
||
# Limitations | ||
|
||
- Currently only supports the banner mediaType | ||
- Assumes that the adUnitCode of the ad is also the id attribute of the element that the ad is rendered into. | ||
- Does not make any attempt to ensure that the ad inside that element is itself visible. It assumes that the publisher is operating in good faith. | ||
|
||
# Params | ||
- enabled [required] [type: boolean, default: false], when set to true, the module will emit BID_VIEWABLE when applicable | ||
|
||
# Example of consuming BID_VIEWABLE event | ||
``` | ||
pbjs.onEvent('bidViewable', function(bid){ | ||
console.log('got bid details in bidViewable event', bid); | ||
}); | ||
``` | ||
|
||
# Example of using config | ||
``` | ||
pbjs.setConfig({ | ||
bidViewability: { | ||
enabled: true, | ||
} | ||
}); | ||
``` | ||
|
||
An example implmentation without an ad server can be found in integrationExamples/postbid/bidViewabilityIO_example.html |
Oops, something went wrong.