The banner loads. The logo fades in. The product rotates. The CTA pulses exactly on cue. Then somebody clicks—and nothing reaches the landing page.
That combination often triggers the most expensive possible response: rebuild the creative. Do not start there. A working animation proves that the browser parsed enough HTML, CSS, and JavaScript to render motion. It does not prove that the pointer reaches the exit layer, that a listener was attached, that the destination exists in the scope your code expects, or that the ad platform recognizes the click contract.
Freeze the current ZIP and test the click path in four links:
- Hit target: What element actually receives the pointer?
- Listener: Did click-handling code bind to that element?
- Exit request: Does the handler request the intended destination during the user gesture?
- Host contract: Does the specific ad product detect, replace, track, and permit that exit?
A failure in the first three links is usually a small code defect. The fourth is usually an export or trafficking mismatch. Neither automatically requires rebuilding the animation.
This article includes three deliberately broken 300×250 banners, the smallest repair for each, six downloadable ZIPs, and recorded tests in a top-level Chromium document and a sandboxed iframe. Open the complete original ClickTag test lab, or use the results below to diagnose your own file first.
First, name the click contract you are actually shipping
“ClickTag” is often used as a generic name for any HTML5 ad exit, but there is no single interchangeable contract across every Google product, much less every ad network.
Google Ad Manager’s HTML5 guidance explicitly says that its article applies to Ad Manager only. For a hand-coded creative, it describes a readable var clickTag in the document head, use of that variable as the destination, and upload-time detection that lets a trafficker change the click-through URL later. Google Ad Manager: HTML5 guidelines
Display & Video 360 gives similar guidance for standard HTML5 display creatives: use a click tag, keep it readable to the ad server, and open the landing page in a new tab or window. Its sample also declares var clickTag and opens window.clickTag. Display & Video 360: Guidelines for HTML5 display creatives
Studio rich-media creatives use a different abstraction. Studio calls named exits through the Enabler, such as Enabler.exit('Background Exit'); the destination URL is supplied after upload. Studio also warns against adding an ordinary anchor around that exit because it can create duplicate windows. Studio: Add an exit
Google Web Designer makes the routing distinction visible at document creation: Google Ads, Display & Video 360, AdMob, and Non-Google Ad are separate environments, and Google says a non-Google export may need additional changes for the eventual host. Google Web Designer: Select the ad environment Google Ads’ uploaded-display specifications likewise require the Google Ads environment for a Google Web Designer build and state that multiple exits are not supported there. Google Ads: Uploaded display ads specifications
| Delivery route | Contract to verify | A common wrong repair |
|---|---|---|
| Hand-coded Google Ad Manager creative | Readable global var clickTag in the head; exit code uses that variable; uploader detects it |
Hard-coding a landing URL inside minified click code |
| Standard DV360 HTML5 display creative | Readable click tag; user click opens the destination in a new context | Treating animation playback as proof that the exit works |
| Studio / CM360 rich media | Enabler is initialized; a named Enabler.exit() is attached to the intended layer |
Adding an anchor or generic clickTag on top of the Studio exit |
| Google Web Designer for Google Ads | Document was built and published for the Google Ads environment; supported exit structure | Exporting for DV360 or “Non-Google” and patching at random |
| Another buyer or network | That buyer’s current integration specification and preview | Assuming a Google pattern is universal |
Write the route at the top of the bug ticket before touching code. “Google HTML5” is not specific enough.
Run a five-minute click-path test
Use the final exported ZIP, not an earlier source folder. Unzip it into a clean directory and serve it locally so relative paths behave like web paths. One simple option is:
python3 -m http.server 8000
Open the primary HTML file through that server, then test in this order.
1. Click while watching for a real browsing context
Do not merely watch the address bar or a status label inside the creative. A click handler can update UI and still fail to open anything. Confirm that the click creates the expected tab or window—or, in a platform preview, that the host records and routes the exit as documented.
Keep the open call in the direct click or keyboard event. Browsers can return null when window.open() is blocked, and popup controls are especially sensitive to whether the call follows a user gesture. MDN: window.open()
2. Ask the browser what is under the pointer
For a 300×250 ad, click the visible CTA and also inspect a point near the center in DevTools:
document.elementFromPoint(150, 125)
If the result is a decorative overlay, canvas, video, or transparent transition layer rather than your exit element, the destination code may be perfectly correct and still never run. Repeat at several points because a moving overlay can make a bug intermittent.
3. Inspect the contract without “fixing” it in the console
For a generic global-clickTag build, start with:
typeof window.clickTag
window.clickTag
document.getElementById('click-area')
A string-valued window.clickTag is evidence that the runtime can see the variable. It is not proof that the ad server will detect it, that a listener exists, or that the click will be tracked. Conversely, undefined immediately explains a handler that explicitly reads window.clickTag.
For a Studio build, inspect the Enabler and the named-exit wiring instead of forcing a generic click tag into the file. The correct diagnostic depends on the route you wrote down in the first step.
4. Test destination replacement
A creative can open its hard-coded default URL yet fail when the ad server substitutes the trafficked URL. In a controlled generic-clickTag test, replace the variable with a harmless destination and click again:
window.clickTag = 'https://example.com/clicktag-smoke-test';
Use the platform’s own override field in its actual preview. Do not take a successful console assignment as evidence that an uploader detected the tag.
5. Repeat inside an iframe, then in the real platform preview
Display ads normally run inside a host document rather than as a top-level tab. Test the same exported files in a representative iframe with the permissions your buyer uses. Then upload to the correct official validator or account preview. Local browser behavior and platform acceptance are separate gates.
Google Ad Manager explicitly tells builders to test their HTML5 code before implementation. Studio’s exit guidance asks teams to test every exit both locally and after upload. Google Ad Manager: HTML5 guidelines Studio: Add an exit
Three broken banners, each with one small repair
The following BrightTrail banners are original fictional teaching files, not campaign creatives. All six versions use the same visible concept and continuing orb animation. Each broken version preserves one click defect; its fixed partner changes only what is needed to restore the exit.
The automated run used Chromium 144.0.7559.96 on September 9, 2026. That recorded run replaced local CSS and JavaScript URLs with data URLs because its environment blocked local navigation; the script order and defer attributes were retained. It observed a running CSS animation, inspected the hit target and listener state, clicked the center of each creative, and captured whether a new browsing context opened. The test ran once as a top-level document and once inside a sandboxed iframe that allowed scripts and popups. For fixed generic-clickTag files, it also injected an about:blank test destination and confirmed that the opened URL changed. No external landing page was requested.
| Example | Broken animation | Local static contract preflight | Broken direct click | Broken iframe click | Fixed direct click | Fixed iframe click | Override test after fix |
|---|---|---|---|---|---|---|---|
| 1. Variable is not global | Running | Fail | No popup | No popup | Popup opened | Popup opened | Passed |
| 2. Overlay captures pointer | Running | Pass | No popup | No popup | Popup opened | Popup opened | Passed |
| 3. Listener runs too early | Running | Pass | No popup | No popup | Popup opened | Popup opened | Passed |
The important result is not “three fixes worked.” It is that two of the three dead-click files passed a document-derived static preflight. Structure and readable clickTag checks did not detect the runtime hit-testing or listener-timing failures. The preflight combined selected public structural checks with an illustrative 600,000-byte internal ZIP ceiling, which is not the Ad Manager or DV360 route limit; it was not a Google upload, approval, or campaign test. Full machine-readable results and source hashes are in the test results JSON, with the human-readable method in the test report.
Example 1: clickTag exists, but not where the handler expects it
The broken file declares a module-scoped constant:
<script type="module">
const clickTag = "https://example.com/hb57/example-01-broken";
</script>
The shared click handler asks for window.clickTag. In the recorded run, the animation moved and the listener was attached, but the runtime returned no global clickTag and opened no popup. A page-level var is exposed as a property of the window global object, which is why the plain global pattern matches the simple Ad Manager and DV360 examples. MDN: Window global object
The minimal repair is intentionally boring:
<script type="text/javascript">
var clickTag = "https://example.com/hb57/example-01-fixed";
</script>
Do not generalize this repair to Studio rich media. It is correct for this generic global-clickTag example because both the handler and the selected delivery contract expect window.clickTag.
Downloads: broken ZIP · fixed ZIP
Example 2: the visual overlay is the real click target
The second broken file has a valid, readable global variable and a correctly bound handler. Its static preflight passes. The problem is CSS: an animated sheen covers the entire stage at a higher stacking level than the exit.
#click-area { z-index: 8; }
.blocking-overlay {
position: absolute;
inset: 0;
z-index: 12;
pointer-events: auto;
}
At the tested point, document.elementFromPoint() returned #blocking-overlay, not #click-area. No click reached the handler.
The fixed file keeps the visual layer but removes it from pointer targeting and restores the exit above it:
.blocking-overlay { pointer-events: none; }
#click-area { z-index: 20; }
For HTML elements, pointer-events: none generally prevents that element from becoming the pointer-event target. MDN: pointer-events Studio’s own QA advice reaches the same practical conclusion from the ad side: clickable exit elements need to sit above other elements. Studio: Add an exit
Be cautious with the blanket fix. If the overlay contains its own controls or intentional exit, disabling pointer events on the whole subtree may remove legitimate interaction. First identify the intended target, then change only the layer that should be decorative.
Downloads: broken ZIP · fixed ZIP
Example 3: the handler executes before the exit exists
The third broken file also passes the local static preflight. It contains a readable global clickTag and click code that names it. But its external script appears in the head without defer:
<script src="click.js"></script>
The browser pauses parsing to execute the file before it has created the later #click-area element. The script gets null, skips addEventListener(), and never returns to bind the handler. In the recorded test, the animation ran and the center hit target was the correct exit layer, yet the listener flag was false and no popup opened.
The one-word repair is:
<script src="click.js" defer></script>
For an external classic script, defer schedules execution after the document has been parsed and before DOMContentLoaded. MDN: the script element Moving the script to the end of the body or binding after DOMContentLoaded can also solve the ordering problem; MDN notes that placing a script immediately before </body> is often sufficient when it must manipulate parsed DOM. MDN: DOMContentLoaded
Downloads: broken ZIP · fixed ZIP
A non-clicking platform preview is not automatically a broken creative
Before editing code, confirm whether the preview is supposed to navigate. Google Ads documents a specific exception: uploaded creatives do not click through in the ad-creation preview or its “View full size ad” preview pop-up. Its troubleshooting page directs users to save the ad and use the full-size-ad link workflow rather than treating that preview behavior as a creative failure. Google Ads: Fix error messages with uploaded ads
That does not mean “ignore the dead click.” It means classify the observation correctly:
- The click fails in a standalone browser and representative iframe: investigate the creative’s hit target, listener, scope, and open call.
- The click works locally but fails in the official host: investigate the export environment, platform API, detected exit, sandbox permissions, and trafficked destination.
- The click is disabled in that documented preview surface: move to the platform’s supported test route before changing code.
- The click opens the default URL but not the overridden one: investigate platform detection and substitution, not animation.
This distinction prevents a working exit from being “fixed” into the wrong contract.
Static acceptance and runtime behavior need separate evidence
A ZIP can satisfy visible structural requirements and still be unclickable. Google Ads lists requirements such as a ZIP containing supported assets, a primary HTML document with doctype, HTML and body elements, an ad.size meta tag, bundled relative resources, and a maximum uploaded size of 600 KB. Google Ads: Uploaded display ads specifications Ad Manager separately checks bundle structure, referenced assets, fixed dimensions, and readable click tags. Google Ad Manager: HTML5 guidelines
Those checks answer “Can the host parse and classify this package?” They cannot, by themselves, answer “Will the intended pixel receive a user’s click after the animation reaches frame 87?”
The reverse is also true. A standalone browser can open a hard-coded URL even though the platform cannot detect, track, replace, or accept the exit. Passing runtime behavior does not waive the uploader contract.
Treat release evidence as three columns:
| Gate | What it proves | What it does not prove |
|---|---|---|
| Package/static validation | Required files, metadata, references, limits, and recognizable contract are present | Pointer reaches the exit; listener executes; popup is permitted |
| Browser behavior test | Real user gesture reaches code and requests a destination in the tested host | Ad server detects or tracks the exit; platform will approve the ZIP |
| Official upload and preview | The selected product accepts and interprets the final package in that account | Every publisher placement or browser behaves identically |
A release note that says only “validated” is incomplete. Record which gate passed.
The preflight checklist to run before a rebuild
Use this against the exact final ZIP and destination configuration:
- Route named: The buyer, product, creative type, and export environment are written down.
- Final package used: The tested ZIP is byte-for-byte the ZIP intended for upload.
- Entry point found: The primary HTML is where the host expects it, and every local reference resolves.
- Dimensions declared: The package contains the required fixed-size metadata for that route.
- Contract readable: Generic clickTag, GWD exit, Studio Enabler exit, or another network API matches the selected host.
- No accidental hard-coded bypass: The landing URL is changeable and trackable in the way the platform requires.
- Hit target verified:
elementFromPoint()returns the intended exit at the CTA, center, corners, and any moving transition area. - Listener verified: The handler binds after its DOM target exists and is not removed during a scene change.
- Direct gesture preserved: The new-context request occurs inside the user click or keyboard action, not after an unrelated timer or rejected promise.
- Standalone behavior passed: A real click opens the controlled test destination.
- Iframe behavior passed: The same click works in a representative embedded host with realistic sandbox permissions.
- Override passed: The platform-supplied destination replaces the default and is the URL actually requested.
- Every exit tested: Multiple named exits are tested individually where the product supports them; unsupported multiple-exit patterns are removed.
- Official gate passed: The correct validator, uploader, saved preview, or trafficking workflow accepts and recognizes the exit.
- Evidence recorded: Browser/version, host mode, package hash, destination used, result, and date are attached to the ticket.
Only after this checklist should “rebuild” become a serious option.
When a rebuild is justified
A rebuild makes sense when the selected export environment is fundamentally wrong, the authoring tool generated an incompatible runtime, or interaction and scene architecture are so entangled that a small repair cannot be isolated safely. It may also be faster when a vendor requires a fresh export to generate its own supported exit components and metadata.
It is not justified because the animation still works, because a preview did not navigate, or because somebody found the string clickTag in the source. Those observations are clues, not diagnoses.
The practical rule is simple: prove the click’s first broken link, repair that link, and rerun all three gates. Rebuilding before that test throws away evidence and risks reproducing the same contract, layering, or timing defect in a prettier file.
Sources
- HTML5 guidelines for Ad Manager — Google Ad Manager Help.
- Guidelines for HTML5 display creatives — Display & Video 360 Help.
- Uploaded display ads specifications — Google Ads Help.
- Fix error messages with uploaded ads — Google Ads Help.
- Select the ad environment — Google Web Designer Help.
- Add an exit — Studio Help.
window.open()— MDN Web Docs.pointer-events— MDN Web Docs.- The
scriptelement — MDN Web Docs. DOMContentLoaded— MDN Web Docs.- Window global object — MDN Web Docs.




