If you are still using a script named ads.js to detect ad blockers, you are living in 2014. Modern blockers like uBlock Origin, AdGuard, and Brave Shields have become incredibly "smart." They don't just block scripts; they perform Cosmetic Filtering—letting your scripts load but hiding the elements from the UI so your detection logic never triggers.
In this post, we’ll explore the industry-standard ways to detect blockers in vanilla JS and modern frameworks like React, Angular, and Vue.
1. The Strategy: Why "Bait & Check" is King
The most reliable way to catch a blocker today is to "bait" it. We create a DOM element that looks exactly like an advertisement to a machine, then we check if the browser has forcibly hidden it.
The Vanilla JavaScript Implementation
We create a div with high-trigger class names (like .ad-placement) and check its offsetHeight.
function isAdBlockActive() {
const bait = document.createElement('div');
// Use classes found in common filter lists like EasyList
bait.className = 'ad-placement adsbox banner-ad';
bait.style.cssText = 'position:fixed; left:-999px; height:1px; width:1px;';
document.body.appendChild(bait);
return new Promise((resolve) => {
// Give the blocker 100ms to "sniff" and hide the element
setTimeout(() => {
const isBlocked = window.getComputedStyle(bait).display === 'none' ||
bait.offsetHeight === 0;
bait.remove();
resolve(isBlocked);
}, 100);
});
}
// Usage
isAdBlockActive().then(active => {
if(active) console.log("AdBlock is doing its job!");
});
2. Framework-Specific Implementations
In the world of SPAs (Single Page Applications), we need to handle component lifecycles so that detection runs only when the component is mounted.
React: The Custom Hook Pattern
Hooks are the cleanest way to share this logic across your dashboard or blog pages.
import { useState, useEffect } from 'react';
export function useAdBlockDetector() {
const [isBlocked, setIsBlocked] = useState(false);
useEffect(() => {
const bait = document.createElement('div');
bait.className = 'pub_300x250 ad-unit';
bait.style.cssText = 'position:absolute; top:-1000px; width:1px; height:1px;';
document.body.appendChild(bait);
const timer = setTimeout(() => {
if (window.getComputedStyle(bait).display === 'none' || bait.offsetHeight === 0) {
setIsBlocked(true);
}
bait.remove();
}, 200);
return () => clearTimeout(timer);
}, []);
return isBlocked;
}
Angular: The Detection Service
For Angular devs, wrapping this in a @Injectable service ensures you can inject the status anywhere in your app.
@Injectable({ providedIn: 'root' })
export class AdDetectorService {
public isBlocked = false;
constructor() {
this.checkStatus();
}
private checkStatus() {
const bait = document.createElement('div');
bait.className = 'ads-container google-ads';
document.body.appendChild(bait);
setTimeout(() => {
this.isBlocked = window.getComputedStyle(bait).display === 'none';
bait.remove();
}, 200);
}
}
3. Top Industry Libraries
If you don't want to maintain your own "bait list" of CSS classes, these libraries stay updated with the latest blocker bypasses:
Check-Adblock: A tiny, modern NPM package (<1KB). Perfect for React/Vite projects.
BlockAdBlock: The gold standard for Vanilla JS. It handles cross-browser edge cases (like Brave's aggressive shields) automatically.
Google Tag Manager (GTM): If your GTM script fails to fire a "heartbeat" event to your server, it’s a non-intrusive way to log blocking stats in your analytics.
4. The "DEAL" Approach: What to do next?
Detecting is only half the battle. The IAB (Interactive Advertising Bureau) recommends the DEAL framework:
Detect: Use the code above.
Explain: Tell the user that ads pay for the server costs.
Ask: Request them to whitelist your site.
Lift/Limit: Either show the content anyway (Lift) or restrict features (Limit).
Pro Tip: Don't be too aggressive. Users are generally okay with ads if they aren't intrusive. Instead of a hard "Blocker Block," try a friendly "Support our Writers" banner.
Conclusion
Ad blockers are part of the modern web ecosystem. By using Mutation Observers or Bait Elements, you can accurately measure your "shadow audience" and make data-driven decisions about your monetisation strategy.
