Files
SkyArtShop/website/public/assets/js/archive/lazy-load-optimized.js
Local Server 0ac69e98c2 Phase 1: Remove obsolete files and standardize all pages
- Standardize script loading on faq, privacy, returns, shipping-info pages
- Archive 14 unused JS files (cart-functions, shopping, cart.js, enhanced versions, etc.)
- Archive 2 unused CSS files (responsive-enhanced, responsive-fixes)
- All pages now use consistent script loading order
- Eliminated shopping.js dependency (not needed after standardization)
2026-01-14 21:21:32 -06:00

211 lines
5.2 KiB
JavaScript

/**
* Optimized Lazy Loading for Images
* Improves page load time by deferring offscreen images
* Uses Intersection Observer API for efficient monitoring
*/
(function () {
"use strict";
// Configuration
const CONFIG = {
rootMargin: "50px", // Start loading 50px before entering viewport
threshold: 0.01,
loadingClass: "lazy-loading",
loadedClass: "lazy-loaded",
errorClass: "lazy-error",
};
// Image cache to prevent duplicate loads
const imageCache = new Set();
/**
* Preload image and return promise
*/
function preloadImage(src) {
if (imageCache.has(src)) {
return Promise.resolve();
}
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => {
imageCache.add(src);
resolve();
};
img.onerror = reject;
img.src = src;
});
}
/**
* Load image with fade-in effect
*/
async function loadImage(img) {
const src = img.dataset.src;
const srcset = img.dataset.srcset;
if (!src) return;
img.classList.add(CONFIG.loadingClass);
try {
// Preload the image
await preloadImage(src);
// Set the actual src
img.src = src;
if (srcset) {
img.srcset = srcset;
}
// Remove data attributes to free memory
delete img.dataset.src;
delete img.dataset.srcset;
// Add loaded class for fade-in animation
img.classList.remove(CONFIG.loadingClass);
img.classList.add(CONFIG.loadedClass);
} catch (error) {
console.error("Failed to load image:", src, error);
img.classList.remove(CONFIG.loadingClass);
img.classList.add(CONFIG.errorClass);
// Set fallback/placeholder if available
if (img.dataset.fallback) {
img.src = img.dataset.fallback;
}
}
}
/**
* Initialize lazy loading with Intersection Observer
*/
function initLazyLoad() {
// Check for browser support
if (!("IntersectionObserver" in window)) {
// Fallback: load all images immediately
console.warn("IntersectionObserver not supported, loading all images");
const images = document.querySelectorAll("img[data-src]");
images.forEach(loadImage);
return;
}
// Create observer
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
const img = entry.target;
loadImage(img);
observer.unobserve(img); // Stop observing once loaded
}
});
},
{
rootMargin: CONFIG.rootMargin,
threshold: CONFIG.threshold,
}
);
// Observe all lazy images
const lazyImages = document.querySelectorAll("img[data-src]");
lazyImages.forEach((img) => observer.observe(img));
// Also observe images added dynamically
const mutationObserver = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
mutation.addedNodes.forEach((node) => {
if (node.nodeName === "IMG" && node.dataset && node.dataset.src) {
observer.observe(node);
}
// Check child images
if (node.querySelectorAll) {
const childImages = node.querySelectorAll("img[data-src]");
childImages.forEach((img) => observer.observe(img));
}
});
});
});
mutationObserver.observe(document.body, {
childList: true,
subtree: true,
});
// Store observers globally for cleanup
window._lazyLoadObservers = { observer, mutationObserver };
}
/**
* Cleanup observers (call on page unload if needed)
*/
function cleanup() {
if (window._lazyLoadObservers) {
const { observer, mutationObserver } = window._lazyLoadObservers;
observer.disconnect();
mutationObserver.disconnect();
}
}
// Add CSS for loading states
function injectStyles() {
if (document.getElementById("lazy-load-styles")) return;
const style = document.createElement("style");
style.id = "lazy-load-styles";
style.textContent = `
img[data-src] {
opacity: 0;
transition: opacity 0.3s ease-in-out;
}
img.lazy-loading {
opacity: 0.5;
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
background-size: 200% 100%;
animation: loading 1.5s infinite;
}
img.lazy-loaded {
opacity: 1;
}
img.lazy-error {
opacity: 0.3;
border: 2px dashed #ccc;
}
@keyframes loading {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}
/* Prevent layout shift */
img[data-src] {
min-height: 200px;
background-color: #f5f5f5;
}
`;
document.head.appendChild(style);
}
// Initialize on DOM ready
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", () => {
injectStyles();
initLazyLoad();
});
} else {
injectStyles();
initLazyLoad();
}
// Export for manual usage
window.LazyLoad = {
init: initLazyLoad,
load: loadImage,
cleanup: cleanup,
};
})();