React - Google Ads / GPT with fluid size on web page
In this article we would like to show trick how in React use Google Ads / GPT in fluid size mode.
By default fluid size mode is not supported on web pages but there is some trick how to scale Ad with transform
scale
style. The main idea of the below solution is to display Google Ad in supported size defined in Google Ads Panel, keeping aspect ratio and scale Ad to the desired size - container size - size for below container component is set with style
property. That approach is useful on mobile devices where we want to scale Ads to fill some area on out web page.
Notes:
- if example doesn't work copy it to your web site and run it under your domain (it can do not work on
localhost
or in local file run in web browser),- go to this article to read about not scalled Google Ads / GPT in React,
- go to this article to see component that scales content with
transform
scale
style what was used in this aticle -ContentStretcher
component.
Hint: in some cases it can be necessary to run
slot.display();
when Ad containg DOM element is ready - you can try to usesetTimeout
with delay>=100ms
as some trick.
Practical example:
// ONLINE-RUNNER:browser;
<!doctype html>
<html>
<head>
<!-- Required only to run React code in this example -->
<script src="https://unpkg.com/react/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<!-- Google Ads / GPT import - attach it to yours project -->
<script src="https://www.googletagservices.com/tag/js/gpt.js"></script>
</head>
<body>
<div id="root"></div>
<!-- copy below script body to React project -->
<script type="text/babel">
// Note: Uncomment import lines during working with JSX Compiler.
// import React from 'react';
// import ReactDOM from 'react-dom';
const googletag = window.googletag || (window.googletag = { cmd: [] });
const createScope = (action) => action && action();
const GPTAdsManager = createScope(() => {
let initialized = false;
const initializeAds = (initialLoading = false, singleRequest = true) => {
if (initialized) {
return;
}
initialized = true;
googletag.cmd.push(() => {
const pubads = googletag.pubads();
if (!initialLoading) {
pubads.disableInitialLoad();
}
if (singleRequest) {
pubads.enableSingleRequest();
}
googletag.enableServices();
});
};
const createSlot = (adPath, adWidth, adHeight, elementId) => {
initializeAds(); // only if not initialized yet
let slot = null;
googletag.cmd.push(() => {
const size = adWidth & adHeight ? [adWidth, adHeight] : ['fluid'];
const tmp = googletag.defineSlot(adPath, size, elementId);
if (tmp) {
slot = tmp;
tmp.addService(googletag.pubads());
}
});
const display = () => {
if (slot) {
googletag.cmd.push(() => {
const pubads = googletag.pubads();
pubads.refresh([slot]);
});
}
};
const refresh = () => {
if (slot) {
googletag.cmd.push(() => {
const pubads = googletag.pubads();
pubads.refresh([slot]);
});
}
};
const destroy = () => {
if (slot) {
const tmp = slot;
googletag.cmd.push(() => {
const pubads = googletag.pubads();
googletag.destroySlots([tmp]);
});
slot = null;
}
};
return { display, refresh, destroy };
}
return { initializeAds, createSlot };
});
const AutoSizer = React.memo(({ interval, children, ...other }) => {
const reference = React.useRef();
const [size, setSize] = React.useState();
React.useEffect(() => {
let storedWidth = size?.width;
let storedHeight = size?.height;
const id = setInterval(() => {
const element = reference.current;
if (element) {
const width = element.offsetWidth;
const height = element.offsetHeight;
if (width != storedWidth || height != storedHeight) {
storedWidth = width;
storedHeight = height;
setSize({ width, height });
}
}
}, interval ?? 100);
return () => {
clearInterval(id);
};
}, [interval]);
return (
<div ref={reference} {...other}>
{size && children && children(size.width, size.height)}
</div>
);
});
const ContentStretcher = ({sizerInterval, contentWidth, contentHeight, children, ...other}) => (
<AutoSizer
{...other}
style={{
...other.style,
position: 'relative',
display: 'flex'
}}
interval={sizerInterval}
>
{(containerWidth, containerHeight) => {
const contentScale =
containerHeight * contentWidth < containerWidth * contentHeight
? containerHeight / contentHeight
: containerWidth / contentWidth;
return (
<div
style={{
position: 'absolute',
left: '50%',
top: '50%',
transform: `translate(-50%, -50%) scale(${contentScale})`,
transformOrigin: '50% 50%',
}}
>
{children}
</div>
);
}}
</AutoSizer>
);
let adCounter = 0;
const Ad = ({ path, width, height }) => {
const id = React.useMemo(() => `div-gpt-ad-${++adCounter}`, []);
React.useEffect(() => {
const slot = GPTAdsManager.createSlot(path, width, height, id);
slot.display();
// slot.refresh(); // forces Ad reloading
return () => {
slot.destroy();
};
}, [path, width, height]);
return (
<div id={id} />
);
};
const FluidAd = ({ adPath, adWidth, adHeight, ...other }) => (
<ContentStretcher
{...other}
contentWidth={adWidth}
contentHeight={adHeight}
>
<Ad path={adPath} width={adWidth} height={adHeight} />
</ContentStretcher>
);
// Usage example:
// somewhere in the code...
GPTAdsManager.initializeAds(false, true);
// uncomment below 3 lines to open GPT Ads console
// window.googletag.cmd.push(() => {
// window.googletag.openConsole();
// });
const App = () => (
<div className="App">
<FluidAd
style={{width: '500px', height: '420px'}}
adPath="/6355419/Travel/Europe/body"
adWidth={300}
adHeight={250}
/>
</div>
);
const root = document.querySelector('#root');
ReactDOM.render(<App />, root );
</script>
</body>
</html>
The article describes the situation in Feb 2021.
Note: be sure that Ad size (
adWidth
andadHeight
) has always the same size to prevent unnecessary Ad recreation - or change it consciously according to new slot configurations.