Instead of lazy loading slots when they enter the viewport (which causes layout shift when unfilled slots collapse), now all slots are activated immediately on page load. - Slots start collapsed (max-height:0, opacity:0) via CSS - All slots are activated with 100ms stagger to avoid rate limiting - Only slots confirmed as 'filled' expand and become visible - Unfilled slots remain collapsed - zero layout shift This completely eliminates the CLS issue where content would jump when ad slots were hidden after entering the viewport. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
667 lines
22 KiB
JavaScript
667 lines
22 KiB
JavaScript
/**
|
|
* AdSense Lazy Loader con Intersection Observer
|
|
*
|
|
* Carga anuncios AdSense individualmente cuando entran al viewport,
|
|
* detecta si reciben contenido, y oculta slots vacios.
|
|
*
|
|
* @package ROI_Theme
|
|
* @since 1.5.0
|
|
* @version 2.0.0 - Refactorizado con Intersection Observer
|
|
*/
|
|
|
|
(function() {
|
|
'use strict';
|
|
|
|
// =========================================================================
|
|
// CONFIGURACION
|
|
// =========================================================================
|
|
|
|
/**
|
|
* Configuracion por defecto, sobrescrita por window.roiAdsenseConfig
|
|
*/
|
|
var DEFAULT_CONFIG = {
|
|
lazyEnabled: true,
|
|
rootMargin: '200px 0px',
|
|
fillTimeout: 5000,
|
|
debug: false
|
|
};
|
|
|
|
/**
|
|
* Obtiene configuracion desde wp_localize_script o usa defaults
|
|
*/
|
|
function getConfig() {
|
|
var wpConfig = window.roiAdsenseConfig || {};
|
|
return {
|
|
lazyEnabled: typeof wpConfig.lazyEnabled !== 'undefined' ? wpConfig.lazyEnabled : DEFAULT_CONFIG.lazyEnabled,
|
|
rootMargin: wpConfig.rootMargin || DEFAULT_CONFIG.rootMargin,
|
|
fillTimeout: typeof wpConfig.fillTimeout !== 'undefined' ? parseInt(wpConfig.fillTimeout, 10) : DEFAULT_CONFIG.fillTimeout,
|
|
debug: typeof wpConfig.debug !== 'undefined' ? wpConfig.debug : DEFAULT_CONFIG.debug
|
|
};
|
|
}
|
|
|
|
var CONFIG = getConfig();
|
|
|
|
// =========================================================================
|
|
// ESTADO GLOBAL
|
|
// =========================================================================
|
|
|
|
var libraryLoaded = false;
|
|
var libraryLoading = false;
|
|
var libraryLoadFailed = false;
|
|
var loadRetryCount = 0;
|
|
var MAX_LOAD_RETRIES = 1;
|
|
var RETRY_DELAY = 2000;
|
|
|
|
/** @type {IntersectionObserver|null} */
|
|
var slotObserver = null;
|
|
|
|
/** @type {Map<Element, MutationObserver>} */
|
|
var fillObservers = new Map();
|
|
|
|
/** @type {Map<Element, number>} */
|
|
var fillTimeouts = new Map();
|
|
|
|
/** @type {Set<Element>} */
|
|
var activatedSlots = new Set();
|
|
|
|
/** @type {Array<Function>} */
|
|
var pendingActivations = [];
|
|
|
|
// =========================================================================
|
|
// LOGGING
|
|
// =========================================================================
|
|
|
|
/**
|
|
* Log condicional basado en CONFIG.debug
|
|
* @param {string} message
|
|
* @param {string} [level='log'] - 'log', 'warn', 'error'
|
|
*/
|
|
function debugLog(message, level) {
|
|
if (!CONFIG.debug || typeof console === 'undefined') {
|
|
return;
|
|
}
|
|
level = level || 'log';
|
|
var prefix = '[AdSense Lazy] ';
|
|
if (level === 'error') {
|
|
console.error(prefix + message);
|
|
} else if (level === 'warn') {
|
|
console.warn(prefix + message);
|
|
} else {
|
|
console.log(prefix + message);
|
|
}
|
|
}
|
|
|
|
// =========================================================================
|
|
// DETECCION DE SOPORTE
|
|
// =========================================================================
|
|
|
|
/**
|
|
* Verifica si el navegador soporta Intersection Observer
|
|
*/
|
|
function hasIntersectionObserverSupport() {
|
|
return typeof window.IntersectionObserver !== 'undefined';
|
|
}
|
|
|
|
/**
|
|
* Verifica si el navegador soporta MutationObserver
|
|
*/
|
|
function hasMutationObserverSupport() {
|
|
return typeof window.MutationObserver !== 'undefined';
|
|
}
|
|
|
|
// =========================================================================
|
|
// CARGA DE BIBLIOTECA ADSENSE
|
|
// =========================================================================
|
|
|
|
/**
|
|
* Carga la biblioteca adsbygoogle.js
|
|
* @param {Function} onSuccess
|
|
* @param {Function} onError
|
|
*/
|
|
function loadAdSenseLibrary(onSuccess, onError) {
|
|
if (libraryLoaded) {
|
|
debugLog('Biblioteca ya cargada');
|
|
onSuccess();
|
|
return;
|
|
}
|
|
|
|
if (libraryLoading) {
|
|
debugLog('Biblioteca en proceso de carga, encolando callback');
|
|
pendingActivations.push(onSuccess);
|
|
return;
|
|
}
|
|
|
|
libraryLoading = true;
|
|
debugLog('Cargando biblioteca adsbygoogle.js...');
|
|
|
|
var scriptTags = document.querySelectorAll('script[data-adsense-script]');
|
|
if (scriptTags.length === 0) {
|
|
debugLog('No se encontro script[data-adsense-script]', 'warn');
|
|
libraryLoading = false;
|
|
onError();
|
|
return;
|
|
}
|
|
|
|
var oldScript = scriptTags[0];
|
|
var newScript = document.createElement('script');
|
|
newScript.src = oldScript.src;
|
|
newScript.async = true;
|
|
|
|
if (oldScript.getAttribute('crossorigin')) {
|
|
newScript.crossOrigin = oldScript.getAttribute('crossorigin');
|
|
}
|
|
|
|
newScript.onload = function() {
|
|
debugLog('Biblioteca cargada exitosamente');
|
|
libraryLoaded = true;
|
|
libraryLoading = false;
|
|
window.adsbygoogle = window.adsbygoogle || [];
|
|
|
|
// Ejecutar callbacks pendientes
|
|
onSuccess();
|
|
while (pendingActivations.length > 0) {
|
|
var callback = pendingActivations.shift();
|
|
callback();
|
|
}
|
|
};
|
|
|
|
newScript.onerror = function() {
|
|
debugLog('Error cargando biblioteca (intento ' + (loadRetryCount + 1) + ')', 'error');
|
|
libraryLoading = false;
|
|
|
|
if (loadRetryCount < MAX_LOAD_RETRIES) {
|
|
loadRetryCount++;
|
|
debugLog('Reintentando en ' + RETRY_DELAY + 'ms...');
|
|
setTimeout(function() {
|
|
loadAdSenseLibrary(onSuccess, onError);
|
|
}, RETRY_DELAY);
|
|
} else {
|
|
debugLog('Maximo de reintentos alcanzado', 'error');
|
|
libraryLoadFailed = true;
|
|
onError();
|
|
}
|
|
};
|
|
|
|
oldScript.parentNode.replaceChild(newScript, oldScript);
|
|
}
|
|
|
|
/**
|
|
* Marca todos los slots como error cuando la biblioteca falla
|
|
*/
|
|
function markAllSlotsAsError() {
|
|
var slots = document.querySelectorAll('.roi-ad-slot[data-ad-lazy="true"]');
|
|
slots.forEach(function(slot) {
|
|
slot.classList.add('roi-ad-error');
|
|
cleanupSlot(slot);
|
|
});
|
|
debugLog('Todos los slots marcados como error', 'error');
|
|
}
|
|
|
|
// =========================================================================
|
|
// ACTIVACION DE SLOTS
|
|
// =========================================================================
|
|
|
|
/**
|
|
* Activa un slot individual ejecutando adsbygoogle.push()
|
|
* @param {Element} slot
|
|
*/
|
|
function activateSlot(slot) {
|
|
if (activatedSlots.has(slot)) {
|
|
debugLog('Slot ya activado, omitiendo');
|
|
return;
|
|
}
|
|
|
|
if (libraryLoadFailed) {
|
|
debugLog('Biblioteca fallida, marcando slot como error');
|
|
slot.classList.add('roi-ad-error');
|
|
return;
|
|
}
|
|
|
|
activatedSlots.add(slot);
|
|
|
|
var doActivation = function() {
|
|
var ins = slot.querySelector('ins.adsbygoogle');
|
|
if (!ins) {
|
|
debugLog('No se encontro <ins> en slot', 'warn');
|
|
slot.classList.add('roi-ad-empty');
|
|
return;
|
|
}
|
|
|
|
debugLog('Activando slot: ' + (ins.getAttribute('data-ad-slot') || 'unknown'));
|
|
|
|
// Ejecutar push
|
|
try {
|
|
window.adsbygoogle = window.adsbygoogle || [];
|
|
window.adsbygoogle.push({});
|
|
} catch (e) {
|
|
debugLog('Error en push: ' + e.message, 'error');
|
|
slot.classList.add('roi-ad-error');
|
|
return;
|
|
}
|
|
|
|
// Iniciar observacion de llenado
|
|
startFillDetection(slot, ins);
|
|
};
|
|
|
|
// Si la biblioteca ya cargo, activar inmediatamente
|
|
if (libraryLoaded) {
|
|
doActivation();
|
|
} else {
|
|
// Cargar biblioteca y luego activar
|
|
loadAdSenseLibrary(doActivation, function() {
|
|
markAllSlotsAsError();
|
|
});
|
|
}
|
|
}
|
|
|
|
// =========================================================================
|
|
// DETECCION DE LLENADO
|
|
// =========================================================================
|
|
|
|
/** @type {Map<Element, number>} */
|
|
var pollIntervals = new Map();
|
|
|
|
/** Intervalo de polling rapido en ms */
|
|
var POLL_INTERVAL = 50;
|
|
|
|
/** Maximo de intentos de polling (50ms * 60 = 3 segundos max) */
|
|
var MAX_POLL_ATTEMPTS = 60;
|
|
|
|
/**
|
|
* Inicia la deteccion de llenado para un slot
|
|
* @param {Element} slot
|
|
* @param {Element} ins
|
|
*/
|
|
function startFillDetection(slot, ins) {
|
|
// Verificar inmediatamente si ya tiene contenido
|
|
if (checkFillStatus(slot, ins)) {
|
|
return;
|
|
}
|
|
|
|
// Estrategia: Polling rapido (50ms) para detectar data-ad-status lo antes posible.
|
|
// AdSense establece data-ad-status muy rapido despues de inyectar el iframe,
|
|
// pero MutationObserver a veces no lo detecta inmediatamente.
|
|
var pollCount = 0;
|
|
var pollId = setInterval(function() {
|
|
pollCount++;
|
|
|
|
if (checkFillStatus(slot, ins)) {
|
|
// Estado detectado, limpiar polling
|
|
clearInterval(pollId);
|
|
pollIntervals.delete(slot);
|
|
return;
|
|
}
|
|
|
|
// Si alcanzamos el maximo de intentos, marcar como vacio
|
|
if (pollCount >= MAX_POLL_ATTEMPTS) {
|
|
debugLog('Polling timeout alcanzado (' + (pollCount * POLL_INTERVAL) + 'ms)');
|
|
clearInterval(pollId);
|
|
pollIntervals.delete(slot);
|
|
markSlotEmpty(slot);
|
|
}
|
|
}, POLL_INTERVAL);
|
|
|
|
pollIntervals.set(slot, pollId);
|
|
}
|
|
|
|
/**
|
|
* Verifica el estado de llenado de un slot
|
|
* @param {Element} slot
|
|
* @param {Element} ins
|
|
* @returns {boolean} true si el estado fue determinado (filled o empty)
|
|
*/
|
|
function checkFillStatus(slot, ins) {
|
|
// IMPORTANTE: Solo data-ad-status es confiable para determinar el estado final.
|
|
// AdSense inyecta iframe ANTES de establecer data-ad-status, por lo que
|
|
// la presencia de iframe NO indica que el anuncio fue llenado.
|
|
|
|
var status = ins.getAttribute('data-ad-status');
|
|
|
|
// Estado definitivo: filled
|
|
if (status === 'filled') {
|
|
debugLog('Slot llenado (data-ad-status=filled)');
|
|
markSlotFilled(slot);
|
|
return true;
|
|
}
|
|
|
|
// Estado definitivo: unfilled (sin anuncio disponible)
|
|
if (status === 'unfilled') {
|
|
debugLog('Slot vacio (data-ad-status=unfilled)');
|
|
markSlotEmpty(slot);
|
|
return true;
|
|
}
|
|
|
|
// Si no hay data-ad-status, AdSense aun no ha respondido.
|
|
// NO usar iframe como criterio porque AdSense inyecta iframe incluso para unfilled.
|
|
// El MutationObserver seguira observando hasta que data-ad-status aparezca o timeout.
|
|
debugLog('Esperando data-ad-status...');
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Marca un slot como llenado
|
|
* @param {Element} slot
|
|
*/
|
|
function markSlotFilled(slot) {
|
|
slot.classList.remove('roi-ad-empty', 'roi-ad-error');
|
|
slot.classList.add('roi-ad-filled');
|
|
cleanupSlot(slot);
|
|
}
|
|
|
|
/**
|
|
* Marca un slot como vacio
|
|
* @param {Element} slot
|
|
*/
|
|
function markSlotEmpty(slot) {
|
|
slot.classList.remove('roi-ad-filled', 'roi-ad-error');
|
|
slot.classList.add('roi-ad-empty');
|
|
cleanupSlot(slot);
|
|
}
|
|
|
|
/**
|
|
* Limpia observadores, timeouts e intervalos de un slot
|
|
* @param {Element} slot
|
|
*/
|
|
function cleanupSlot(slot) {
|
|
// Limpiar polling interval
|
|
if (pollIntervals.has(slot)) {
|
|
clearInterval(pollIntervals.get(slot));
|
|
pollIntervals.delete(slot);
|
|
}
|
|
|
|
// Limpiar timeout (legacy)
|
|
if (fillTimeouts.has(slot)) {
|
|
clearTimeout(fillTimeouts.get(slot));
|
|
fillTimeouts.delete(slot);
|
|
}
|
|
|
|
// Limpiar MutationObserver (legacy)
|
|
if (fillObservers.has(slot)) {
|
|
fillObservers.get(slot).disconnect();
|
|
fillObservers.delete(slot);
|
|
}
|
|
|
|
// Dejar de observar con IntersectionObserver
|
|
if (slotObserver) {
|
|
slotObserver.unobserve(slot);
|
|
}
|
|
}
|
|
|
|
// =========================================================================
|
|
// INTERSECTION OBSERVER
|
|
// =========================================================================
|
|
|
|
/**
|
|
* Inicializa el Intersection Observer para slots
|
|
*/
|
|
function initIntersectionObserver() {
|
|
if (!hasIntersectionObserverSupport()) {
|
|
debugLog('Sin soporte Intersection Observer, usando modo legacy', 'warn');
|
|
return false;
|
|
}
|
|
|
|
var options = {
|
|
root: null,
|
|
rootMargin: CONFIG.rootMargin,
|
|
threshold: 0
|
|
};
|
|
|
|
slotObserver = new IntersectionObserver(function(entries) {
|
|
entries.forEach(function(entry) {
|
|
if (entry.isIntersecting) {
|
|
var slot = entry.target;
|
|
debugLog('Slot entro al viewport');
|
|
activateSlot(slot);
|
|
}
|
|
});
|
|
}, options);
|
|
|
|
debugLog('Intersection Observer inicializado con rootMargin: ' + CONFIG.rootMargin);
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Observa todos los slots lazy en la pagina
|
|
*/
|
|
function observeAllSlots() {
|
|
var slots = document.querySelectorAll('.roi-ad-slot[data-ad-lazy="true"]');
|
|
debugLog('Encontrados ' + slots.length + ' slots para observar');
|
|
|
|
slots.forEach(function(slot) {
|
|
if (!activatedSlots.has(slot)) {
|
|
slotObserver.observe(slot);
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Observa nuevos slots agregados dinamicamente
|
|
*/
|
|
function observeNewSlots() {
|
|
var slots = document.querySelectorAll('.roi-ad-slot[data-ad-lazy="true"]');
|
|
var newCount = 0;
|
|
|
|
slots.forEach(function(slot) {
|
|
if (!activatedSlots.has(slot)) {
|
|
slotObserver.observe(slot);
|
|
newCount++;
|
|
}
|
|
});
|
|
|
|
if (newCount > 0) {
|
|
debugLog('Agregados ' + newCount + ' nuevos slots al observer');
|
|
}
|
|
}
|
|
|
|
// =========================================================================
|
|
// MODO LEGACY (FALLBACK)
|
|
// =========================================================================
|
|
|
|
/**
|
|
* Variables para modo legacy
|
|
*/
|
|
var legacyLoaded = false;
|
|
var legacyTimeout = null;
|
|
|
|
/**
|
|
* Carga todos los ads en modo legacy (sin Intersection Observer)
|
|
*/
|
|
function loadAllAdsLegacy() {
|
|
if (legacyLoaded) {
|
|
return;
|
|
}
|
|
legacyLoaded = true;
|
|
debugLog('Modo legacy: Cargando todos los ads');
|
|
|
|
if (legacyTimeout) {
|
|
clearTimeout(legacyTimeout);
|
|
}
|
|
removeLegacyEventListeners();
|
|
|
|
loadAdSenseLibrary(function() {
|
|
executeAllPushScripts();
|
|
}, function() {
|
|
debugLog('Error en modo legacy', 'error');
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Ejecuta todos los scripts de push en modo legacy
|
|
*/
|
|
function executeAllPushScripts() {
|
|
var pushScripts = document.querySelectorAll('script[data-adsense-push]');
|
|
debugLog('Ejecutando ' + pushScripts.length + ' scripts de push');
|
|
|
|
window.adsbygoogle = window.adsbygoogle || [];
|
|
|
|
pushScripts.forEach(function(oldScript) {
|
|
var newScript = document.createElement('script');
|
|
newScript.innerHTML = oldScript.innerHTML;
|
|
newScript.type = 'text/javascript';
|
|
oldScript.parentNode.replaceChild(newScript, oldScript);
|
|
});
|
|
|
|
document.body.classList.add('adsense-loaded');
|
|
}
|
|
|
|
/**
|
|
* Event handler para modo legacy
|
|
*/
|
|
function handleLegacyInteraction() {
|
|
debugLog('Interaccion detectada (modo legacy)');
|
|
loadAllAdsLegacy();
|
|
}
|
|
|
|
/**
|
|
* Agrega listeners para modo legacy
|
|
*/
|
|
function addLegacyEventListeners() {
|
|
window.addEventListener('scroll', handleLegacyInteraction, { passive: true, once: true });
|
|
window.addEventListener('mousemove', handleLegacyInteraction, { passive: true, once: true });
|
|
window.addEventListener('touchstart', handleLegacyInteraction, { passive: true, once: true });
|
|
window.addEventListener('click', handleLegacyInteraction, { passive: true, once: true });
|
|
window.addEventListener('keydown', handleLegacyInteraction, { passive: true, once: true });
|
|
}
|
|
|
|
/**
|
|
* Remueve listeners de modo legacy
|
|
*/
|
|
function removeLegacyEventListeners() {
|
|
window.removeEventListener('scroll', handleLegacyInteraction, { passive: true });
|
|
window.removeEventListener('mousemove', handleLegacyInteraction, { passive: true });
|
|
window.removeEventListener('touchstart', handleLegacyInteraction, { passive: true });
|
|
window.removeEventListener('click', handleLegacyInteraction, { passive: true });
|
|
window.removeEventListener('keydown', handleLegacyInteraction, { passive: true });
|
|
}
|
|
|
|
/**
|
|
* Inicia modo legacy con listeners de interaccion
|
|
*/
|
|
function initLegacyMode() {
|
|
debugLog('Iniciando modo legacy');
|
|
addLegacyEventListeners();
|
|
|
|
legacyTimeout = setTimeout(function() {
|
|
debugLog('Timeout legacy alcanzado');
|
|
loadAllAdsLegacy();
|
|
}, CONFIG.fillTimeout);
|
|
}
|
|
|
|
// =========================================================================
|
|
// EVENTO DINAMICO
|
|
// =========================================================================
|
|
|
|
/**
|
|
* Configura listener para ads dinamicos
|
|
*/
|
|
function setupDynamicAdsListener() {
|
|
window.addEventListener('roi-adsense-activate', function() {
|
|
debugLog('Evento roi-adsense-activate recibido');
|
|
|
|
if (CONFIG.lazyEnabled && slotObserver) {
|
|
observeNewSlots();
|
|
} else if (!legacyLoaded) {
|
|
loadAllAdsLegacy();
|
|
} else {
|
|
// Ya cargado en legacy, ejecutar nuevos push
|
|
activateDynamicSlotsLegacy();
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Activa slots dinamicos en modo legacy
|
|
*/
|
|
function activateDynamicSlotsLegacy() {
|
|
var pendingPushScripts = document.querySelectorAll('script[data-adsense-push][type="text/plain"]');
|
|
if (pendingPushScripts.length === 0) {
|
|
return;
|
|
}
|
|
|
|
debugLog('Activando ' + pendingPushScripts.length + ' slots dinamicos (legacy)');
|
|
window.adsbygoogle = window.adsbygoogle || [];
|
|
|
|
pendingPushScripts.forEach(function(oldScript) {
|
|
try {
|
|
var newScript = document.createElement('script');
|
|
newScript.type = 'text/javascript';
|
|
newScript.innerHTML = oldScript.innerHTML;
|
|
oldScript.parentNode.replaceChild(newScript, oldScript);
|
|
} catch (e) {
|
|
debugLog('Error activando slot dinamico: ' + e.message, 'error');
|
|
}
|
|
});
|
|
}
|
|
|
|
// =========================================================================
|
|
// 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
|
|
*/
|
|
function init() {
|
|
// Siempre configurar listener para ads dinamicos
|
|
setupDynamicAdsListener();
|
|
debugLog('Listener dinamico configurado');
|
|
|
|
// Verificar si delay esta habilitado globalmente
|
|
if (!window.roiAdsenseDelayed) {
|
|
debugLog('Delay global no habilitado');
|
|
return;
|
|
}
|
|
|
|
debugLog('Inicializando AdSense Lazy Loader v2.1 (Eager Mode)');
|
|
debugLog('Config: lazyEnabled=' + CONFIG.lazyEnabled + ', fillTimeout=' + CONFIG.fillTimeout);
|
|
|
|
// Decidir modo de operacion
|
|
if (!CONFIG.lazyEnabled) {
|
|
debugLog('Lazy loading deshabilitado, usando modo legacy');
|
|
initLegacyMode();
|
|
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');
|
|
|
|
// Esperar a que el DOM este listo
|
|
if (document.readyState === 'interactive' || document.readyState === 'complete') {
|
|
activateAllSlotsEagerly();
|
|
} else {
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
activateAllSlotsEagerly();
|
|
});
|
|
}
|
|
}
|
|
|
|
// Iniciar
|
|
init();
|
|
|
|
})();
|