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 <noreply@anthropic.com>
664 lines
22 KiB
JavaScript
664 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
|
|
*
|
|
* 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: '600px 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');
|
|
}
|
|
});
|
|
}
|
|
|
|
// =========================================================================
|
|
// 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
|
|
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.2 (IO + Google Official CSS)');
|
|
debugLog('Config: lazyEnabled=' + CONFIG.lazyEnabled + ', rootMargin=' + CONFIG.rootMargin + ', fillTimeout=' + CONFIG.fillTimeout);
|
|
|
|
// Decidir modo de operacion
|
|
if (!CONFIG.lazyEnabled) {
|
|
debugLog('Lazy loading deshabilitado, usando modo legacy');
|
|
initLegacyMode();
|
|
return;
|
|
}
|
|
|
|
// Verificar soporte para Intersection Observer
|
|
if (!hasIntersectionObserverSupport()) {
|
|
debugLog('Sin soporte IO, usando modo legacy', 'warn');
|
|
initLegacyMode();
|
|
return;
|
|
}
|
|
|
|
// 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') {
|
|
observeAllSlots();
|
|
} else {
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
observeAllSlots();
|
|
});
|
|
}
|
|
}
|
|
|
|
// Iniciar
|
|
init();
|
|
|
|
})();
|