From a2dfd10f9e13afb5b1b2f4b5e14e55009bac440c Mon Sep 17 00:00:00 2001 From: FrankZamora Date: Wed, 10 Dec 2025 17:29:44 -0600 Subject: [PATCH] fix(js): implement google official css for unfilled adsense slots MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove eager loading, revert to Intersection Observer with 600px rootMargin. Use google css: ins[data-ad-status=unfilled]{display:none} 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- Assets/Js/adsense-loader.js | 65 ++++++------- .../Ui/AdsensePlacementRenderer.php | 95 ++++++++++--------- functions.php | 2 +- 3 files changed, 81 insertions(+), 81 deletions(-) diff --git a/Assets/Js/adsense-loader.js b/Assets/Js/adsense-loader.js index 5d78f60d..6faba25d 100644 --- a/Assets/Js/adsense-loader.js +++ b/Assets/Js/adsense-loader.js @@ -18,10 +18,15 @@ /** * Configuracion por defecto, sobrescrita por window.roiAdsenseConfig + * + * rootMargin: 600px precarga slots 600px antes de entrar al viewport. + * Esto da tiempo suficiente para que AdSense cargue el anuncio antes + * de que el usuario llegue al slot, evitando layout shift. + * Basado en best practices: https://support.google.com/adsense/answer/10762946 */ var DEFAULT_CONFIG = { lazyEnabled: true, - rootMargin: '200px 0px', + rootMargin: '600px 0px', fillTimeout: 5000, debug: false }; @@ -593,33 +598,19 @@ }); } - // ========================================================================= - // EAGER LOADING (ACTIVACION INMEDIATA) - // ========================================================================= - - /** - * Activa todos los slots inmediatamente sin esperar viewport. - * Los slots inician colapsados (CSS) y solo se expanden si reciben anuncio. - * Esto evita layout shift porque el usuario nunca ve espacios vacios. - */ - function activateAllSlotsEagerly() { - var slots = document.querySelectorAll('.roi-ad-slot[data-ad-lazy="true"]'); - debugLog('Activando ' + slots.length + ' slots de manera eager'); - - slots.forEach(function(slot, index) { - // Pequeño delay escalonado para no saturar AdSense - setTimeout(function() { - activateSlot(slot); - }, index * 100); // 100ms entre cada slot - }); - } - // ========================================================================= // INICIALIZACION // ========================================================================= /** * Inicializa el sistema + * + * ESTRATEGIA v2.2 (basada en documentacion oficial de Google): + * - Los slots NO estan ocultos inicialmente (Google puede no ejecutar requests para slots ocultos) + * - Usamos Intersection Observer con rootMargin grande (600px) para precargar + * - Google automaticamente oculta slots unfilled via CSS: ins[data-ad-status="unfilled"] + * - Nuestro CSS colapsa el contenedor .roi-ad-slot cuando el ins tiene unfilled + * - Esto funciona MEJOR que eager loading porque no satura AdSense con requests simultaneos */ function init() { // Siempre configurar listener para ads dinamicos @@ -632,8 +623,8 @@ return; } - debugLog('Inicializando AdSense Lazy Loader v2.1 (Eager Mode)'); - debugLog('Config: lazyEnabled=' + CONFIG.lazyEnabled + ', fillTimeout=' + CONFIG.fillTimeout); + debugLog('Inicializando AdSense Lazy Loader v2.2 (IO + Google Official CSS)'); + debugLog('Config: lazyEnabled=' + CONFIG.lazyEnabled + ', rootMargin=' + CONFIG.rootMargin + ', fillTimeout=' + CONFIG.fillTimeout); // Decidir modo de operacion if (!CONFIG.lazyEnabled) { @@ -642,20 +633,26 @@ return; } - // NUEVA ESTRATEGIA: Eager loading - // En lugar de esperar a que los slots entren al viewport (lo cual causa - // layout shift cuando se ocultan slots vacios), activamos todos los slots - // inmediatamente. Los slots inician colapsados via CSS y solo se expanden - // cuando AdSense confirma que tienen anuncio (filled). - // Esto elimina completamente el layout shift. - debugLog('Usando modo eager: activar todos los slots inmediatamente'); + // Verificar soporte para Intersection Observer + if (!hasIntersectionObserverSupport()) { + debugLog('Sin soporte IO, usando modo legacy', 'warn'); + initLegacyMode(); + return; + } - // Esperar a que el DOM este listo + // Inicializar Intersection Observer + if (!initIntersectionObserver()) { + debugLog('Fallo inicializando IO, usando modo legacy', 'warn'); + initLegacyMode(); + return; + } + + // Esperar a que el DOM este listo y observar slots if (document.readyState === 'interactive' || document.readyState === 'complete') { - activateAllSlotsEagerly(); + observeAllSlots(); } else { document.addEventListener('DOMContentLoaded', function() { - activateAllSlotsEagerly(); + observeAllSlots(); }); } } diff --git a/Public/AdsensePlacement/Infrastructure/Ui/AdsensePlacementRenderer.php b/Public/AdsensePlacement/Infrastructure/Ui/AdsensePlacementRenderer.php index ba05bee1..50e6a9fa 100644 --- a/Public/AdsensePlacement/Infrastructure/Ui/AdsensePlacementRenderer.php +++ b/Public/AdsensePlacement/Infrastructure/Ui/AdsensePlacementRenderer.php @@ -66,56 +66,59 @@ final class AdsensePlacementRenderer // 4. Generar CSS (usando CSSGeneratorService) $lazyEnabled = ($settings['behavior']['lazy_loading_enabled'] ?? true) === true; - // Estrategia para evitar layout shift: - // - Los slots inician colapsados (max-height:0) sin ocupar espacio visual - // - Usamos max-height en vez de height para que IO pueda detectarlos - // - Se pre-cargan con rootMargin grande (1000px) antes de ser visibles - // - Solo se expanden cuando AdSense confirma que hay anuncio (filled) - // - Si no hay anuncio (unfilled), permanecen colapsados - // - Esto evita que el usuario vea espacios que luego desaparecen - if ($lazyEnabled) { - $css = $this->cssGenerator->generate( - ".roi-ad-slot", - [ - 'max_height' => '0', - 'overflow' => 'hidden', - 'opacity' => '0', - 'width' => '100%', - 'min_width' => '300px', - 'text_align' => 'center', - 'margin' => '0', - 'padding' => '0', - 'transition' => 'max-height 0.3s ease, opacity 0.3s ease, margin 0.3s ease', - ] - ); - // Slots con anuncio confirmado: se expanden y muestran - $css .= $this->cssGenerator->generate('.roi-ad-slot.roi-ad-filled', [ - 'max_height' => '600px', - 'opacity' => '1', - 'overflow' => 'visible', + // Estrategia basada en documentación oficial de Google AdSense: + // https://support.google.com/adsense/answer/10762946 + // + // IMPORTANTE: NO ocultar slots inicialmente porque AdSense puede no + // ejecutar la solicitud de anuncios para slots ocultos. + // + // La solución correcta es: + // 1. Mostrar slots con min-height para reservar espacio (evita CLS) + // 2. Usar CSS oficial de Google para ocultar slots unfilled: + // ins.adsbygoogle[data-ad-status="unfilled"] { display: none !important; } + // 3. El contenedor colapsa automáticamente cuando el ins se oculta + + // CSS base para todos los slots + $css = $this->cssGenerator->generate( + ".roi-ad-slot", + [ + 'width' => '100%', + 'min_width' => '300px', 'margin_top' => '1.5rem', 'margin_bottom' => '1.5rem', - ]); - // Slots vacios: permanecen colapsados sin ocupar espacio - $css .= $this->cssGenerator->generate('.roi-ad-slot.roi-ad-empty', [ - 'max_height' => '0', - 'opacity' => '0', + 'text_align' => 'center', + 'min_height' => '100px', // Reservar espacio mínimo para evitar CLS + ] + ); + + // Solución oficial de Google para ocultar slots sin anuncios + // Ref: https://support.google.com/adsense/answer/10762946 + $css .= $this->cssGenerator->generate( + "ins.adsbygoogle[data-ad-status='unfilled']", + [ + 'display' => 'none !important', + ] + ); + + // Cuando el ins está oculto, colapsar el contenedor también + // Usamos :has() para detectar cuando el ins hijo está unfilled + // Nota: :has() tiene buen soporte en navegadores modernos (2023+) + $css .= $this->cssGenerator->generate( + ".roi-ad-slot:has(ins.adsbygoogle[data-ad-status='unfilled'])", + [ + 'min_height' => '0', 'margin' => '0', 'padding' => '0', - ]); - } else { - // Modo legacy: slots siempre visibles - $css = $this->cssGenerator->generate( - ".roi-ad-slot", - [ - 'width' => '100%', - 'min_width' => '300px', - 'margin_top' => '1.5rem', - 'margin_bottom' => '1.5rem', - 'text_align' => 'center', - ] - ); - } + ] + ); + + // Fallback para navegadores sin soporte :has() - usar clase JS + $css .= $this->cssGenerator->generate('.roi-ad-slot.roi-ad-empty', [ + 'min_height' => '0', + 'margin' => '0', + 'padding' => '0', + 'display' => 'none', + ]); // 5. Generar HTML del anuncio $html = $this->buildAdHTML( diff --git a/functions.php b/functions.php index 9bfb08c7..6333f62d 100644 --- a/functions.php +++ b/functions.php @@ -18,7 +18,7 @@ if (!defined('ABSPATH')) { } // Definir constante de versión del tema -define('ROI_VERSION', '1.0.26'); +define('ROI_VERSION', '1.0.27'); // ============================================================================= // 1. CARGAR AUTOLOADER MANUAL