(function(window, document) {
// Конфигурация по умолчанию
var defaultConfig = {
preconnectDomains: [
'https://fonts.googleapis.com',
'https://fonts.gstatic.com',
'https://cdnjs.cloudflare.com'
],
preloadFonts: [], // массив URL шрифтов
criticalSelectors: ['.header', '.hero'], // селекторы для приоритетной загрузки
lazyLoadImages: true,
asyncCSS: true, // асинхронная загрузка всех CSS
deferScripts: true,
enableFCPReport: false
};
// Объединение конфигурации
var config = window.FCPOptimizerConfig || defaultConfig;
// Вспомогательные функции
function addLinkRel(type, attrs) {
var link = document.createElement('link');
for (var key in attrs) {
link.setAttribute(key, attrs[key]);
}
link.rel = type;
document.head.appendChild(link);
}
function addPreconnect(domain) {
addLinkRel('preconnect', { href: domain });
addLinkRel('dns-prefetch', { href: domain });
}
// Ранние preconnect
if (config.preconnectDomains && config.preconnectDomains.length) {
config.preconnectDomains.forEach(addPreconnect);
}
// Предзагрузка шрифтов
if (config.preloadFonts && config.preloadFonts.length) {
config.preloadFonts.forEach(function(fontUrl) {
addLinkRel('preload', { href: fontUrl, as: 'font', type: 'font/woff2', crossorigin: 'anonymous' });
});
}
// Функция для асинхронной загрузки CSS
function asyncLoadCSS(href, media) {
var link = document.createElement('link');
link.rel = 'stylesheet';
link.href = href;
link.media = media || 'print';
link.onload = function() {
link.media = 'all';
};
document.head.appendChild(link);
}
// Преобразование существующих CSS link в асинхронные
if (config.asyncCSS) {
var links = document.querySelectorAll('link[rel="stylesheet"]:not([data-optimized])');
links.forEach(function(link) {
var href = link.href;
if (href && !link.hasAttribute('data-critical')) {
// Сохраняем оригинальный media
var media = link.media || 'all';
// Создаем новый асинхронный link
var asyncLink = document.createElement('link');
asyncLink.rel = 'preload';
asyncLink.as = 'style';
asyncLink.href = href;
asyncLink.onload = function() {
var styleLink = document.createElement('link');
styleLink.rel = 'stylesheet';
styleLink.href = href;
styleLink.media = media;
document.head.appendChild(styleLink);
};
document.head.appendChild(asyncLink);
// Удаляем оригинальный или помечаем, чтобы не загружался дважды
link.setAttribute('data-optimized', 'true');
// Удаляем оригинальный link, чтобы он не загружался
link.parentNode.removeChild(link);
}
});
}
// Дефер скриптов
if (config.deferScripts) {
var scripts = document.querySelectorAll('script:not([async]):not([defer]):not([data-critical])');
scripts.forEach(function(script) {
if (script.src && !script.hasAttribute('data-optimized')) {
var newScript = document.createElement('script');
newScript.src = script.src;
newScript.defer = true;
if (script.innerHTML) {
newScript.innerHTML = script.innerHTML;
}
script.parentNode.replaceChild(newScript, script);
newScript.setAttribute('data-optimized', 'true');
}
});
}
// Ленивая загрузка изображений
if (config.lazyLoadImages && 'IntersectionObserver' in window) {
var lazyImages = document.querySelectorAll('img:not([loading])');
var imageObserver = new IntersectionObserver(function(entries, observer) {
entries.forEach(function(entry) {
if (entry.isIntersecting) {
var img = entry.target;
if (img.dataset.src) {
img.src = img.dataset.src;
img.removeAttribute('data-src');
}
img.loading = 'lazy';
imageObserver.unobserve(img);
}
});
});
lazyImages.forEach(function(img) {
if (!img.hasAttribute('data-src') && img.src) {
img.setAttribute('data-src', img.src);
img.removeAttribute('src');
// Плейсхолдер можно установить прозрачный или low-res
img.src = 'data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1 1"%3E%3C/svg%3E';
}
imageObserver.observe(img);
});
} else if (config.lazyLoadImages) {
// Fallback для старых браузеров: установить loading="lazy" нативным способом
var imgs = document.querySelectorAll('img');
imgs.forEach(function(img) {
if (!img.loading) img.loading = 'lazy';
});
}
// Приоритетная загрузка критических элементов (изображения выше сгиба)
if (config.criticalSelectors && config.criticalSelectors.length) {
var criticalElements = document.querySelectorAll(config.criticalSelectors.join(','));
criticalElements.forEach(function(el) {
var img = el.querySelector('img');
if (img && img.src) {
// Устанавливаем fetchpriority high для важных изображений
img.fetchPriority = 'high';
// Убираем ленивую загрузку для критических изображений
img.loading = 'eager';
}
});
}
// Отчет по FCP, если включено
if (config.enableFCPReport && 'PerformanceObserver' in window) {
var fcpObserver = new PerformanceObserver(function(list) {
var entries = list.getEntries();
entries.forEach(function(entry) {
if (entry.name === 'first-contentful-paint') {
console.log('[FCP Optimizer] First Contentful Paint:', entry.startTime, 'ms');
// Можно отправить в аналитику
if (window.gtag) {
window.gtag('event', 'timing', { name: 'FCP', value: Math.round(entry.startTime) });
}
}
});
});
fcpObserver.observe({ type: 'paint', buffered: true });
}
// Используем requestIdleCallback для некритичных операций
if ('requestIdleCallback' in window) {
requestIdleCallback(function() {
// Предзагрузка следующей страницы (если есть ссылки prefetch)
var prefetchLinks = document.querySelectorAll('link[rel="prefetch"]');
if (prefetchLinks.length === 0) {
// Можно добавить предзагрузку для следующей страницы по hover
var navLinks = document.querySelectorAll('a');
navLinks.forEach(function(link) {
if (link.href && link.href.indexOf(window.location.origin) === 0) {
link.addEventListener('mouseenter', function() {
var prefetch = document.createElement('link');
prefetch.rel = 'prefetch';
prefetch.href = link.href;
document.head.appendChild(prefetch);
}, { once: true });
}
});
}
});
}
})(window, document);