Spaces:
Sleeping
Sleeping
| /* Performance Optimization & Lazy Loading */ | |
| class PerformanceOptimizer { | |
| constructor() { | |
| this.init(); | |
| } | |
| init() { | |
| this.setupLazyLoading(); | |
| this.optimizeAnimations(); | |
| this.setupImageLoadingStates(); | |
| this.optimizeScrollEvents(); | |
| this.preloadCriticalAssets(); | |
| } | |
| // Lazy Loading with Intersection Observer | |
| setupLazyLoading() { | |
| const imageObserver = new IntersectionObserver((entries, observer) => { | |
| entries.forEach(entry => { | |
| if (entry.isIntersecting) { | |
| const img = entry.target; | |
| // Add loading state | |
| img.classList.add('loading'); | |
| // Load the image | |
| if (img.dataset.src) { | |
| img.src = img.dataset.src; | |
| img.removeAttribute('data-src'); | |
| } | |
| // Handle load completion | |
| img.onload = () => { | |
| img.classList.remove('loading'); | |
| img.classList.add('loaded'); | |
| }; | |
| // Handle errors | |
| img.onerror = () => { | |
| img.classList.remove('loading'); | |
| img.classList.add('error'); | |
| // Set fallback image | |
| img.src = 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDAwIiBoZWlnaHQ9IjMwMCIgdmlld0JveD0iMCAwIDQwMCAzMDAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSI0MDAiIGhlaWdodD0iMzAwIiBmaWxsPSIjMWExYTJlIi8+CjxwYXRoIGQ9Ik0yMDAgMTAwQzIyMi4wOTEgMTAwIDI0MCA4Mi4wOTE0IDI0MCA2MEM2MCAzNy45MDg2IDIyMi4wOTEgMjAgMjAwIDIwQzE3Ny45MDkgMjAgMTYwIDM3LjkwODYgMTYwIDYwQzE2MCA4Mi4wOTE0IDE3Ny45MDkgMTAwIDIwMCAxMDBaIiBmaWxsPSIjNjY2NjY2Ii8+CjxyZWN0IHg9IjEwMCIgeT0iMTUwIiB3aWR0aD0iMjAwIiBoZWlnaHQ9IjIwIiByeD0iMTAiIGZpbGw9IiM2NjY2NjYiLz4KPHJlY3QgeD0iMTMwIiB5PSIxOTAiIHdpZHRoPSIxNDAiIGhlaWdodD0iMTUiIHJ4PSI3LjUiIGZpbGw9IiM0NDQ0NDQiLz4KPC9zdmc+'; | |
| }; | |
| observer.unobserve(img); | |
| } | |
| }); | |
| }, { | |
| rootMargin: '50px 0px', | |
| threshold: 0.01 | |
| }); | |
| // Observe all images with data-src attribute | |
| document.querySelectorAll('img[data-src]').forEach(img => { | |
| imageObserver.observe(img); | |
| }); | |
| // Also setup for dynamically loaded images | |
| this.setupDynamicImageObserver(imageObserver); | |
| } | |
| setupDynamicImageObserver(observer) { | |
| // Watch for new images added to the DOM | |
| const mutationObserver = new MutationObserver(mutations => { | |
| mutations.forEach(mutation => { | |
| mutation.addedNodes.forEach(node => { | |
| if (node.nodeType === 1) { // Element node | |
| const images = node.querySelectorAll ? node.querySelectorAll('img[data-src]') : []; | |
| images.forEach(img => observer.observe(img)); | |
| // If the node itself is an image | |
| if (node.tagName === 'IMG' && node.hasAttribute('data-src')) { | |
| observer.observe(node); | |
| } | |
| } | |
| }); | |
| }); | |
| }); | |
| mutationObserver.observe(document.body, { | |
| childList: true, | |
| subtree: true | |
| }); | |
| } | |
| // Optimize animations using requestAnimationFrame | |
| optimizeAnimations() { | |
| let ticking = false; | |
| function updateAnimations() { | |
| // Batch DOM updates here | |
| ticking = false; | |
| } | |
| function requestTick() { | |
| if (!ticking) { | |
| requestAnimationFrame(updateAnimations); | |
| ticking = true; | |
| } | |
| } | |
| // Throttle scroll events | |
| window.addEventListener('scroll', requestTick, { passive: true }); | |
| } | |
| // Add loading states for images | |
| setupImageLoadingStates() { | |
| // Add CSS for loading states if not already present | |
| if (!document.getElementById('lazy-loading-styles')) { | |
| const style = document.createElement('style'); | |
| style.id = 'lazy-loading-styles'; | |
| style.textContent = ` | |
| img.loading { | |
| background: linear-gradient(90deg, #1a1a2e 25%, #2d2d4a 50%, #1a1a2e 75%); | |
| background-size: 200% 100%; | |
| animation: loading-shimmer 1.5s infinite; | |
| min-height: 200px; | |
| } | |
| img.loaded { | |
| animation: fadeIn 0.3s ease-in-out; | |
| } | |
| img.error { | |
| background: #333; | |
| color: #999; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| min-height: 200px; | |
| } | |
| @keyframes loading-shimmer { | |
| 0% { background-position: -200% 0; } | |
| 100% { background-position: 200% 0; } | |
| } | |
| @keyframes fadeIn { | |
| from { opacity: 0; } | |
| to { opacity: 1; } | |
| } | |
| .card-img-top { | |
| transition: transform 0.3s ease; | |
| } | |
| .card:hover .card-img-top { | |
| transform: scale(1.05); | |
| } | |
| `; | |
| document.head.appendChild(style); | |
| } | |
| } | |
| // Optimize scroll events | |
| optimizeScrollEvents() { | |
| let scrollTimeout; | |
| window.addEventListener('scroll', () => { | |
| // Clear existing timeout | |
| clearTimeout(scrollTimeout); | |
| // Set a new timeout | |
| scrollTimeout = setTimeout(() => { | |
| // Update scroll progress | |
| this.updateScrollProgress(); | |
| }, 10); | |
| }, { passive: true }); | |
| } | |
| updateScrollProgress() { | |
| const scrollProgress = document.getElementById('scrollProgress'); | |
| if (scrollProgress) { | |
| const scrollTop = window.pageYOffset || document.documentElement.scrollTop; | |
| const docHeight = document.body.scrollHeight - window.innerHeight; | |
| const scrollPercent = (scrollTop / docHeight) * 100; | |
| scrollProgress.style.width = Math.min(scrollPercent, 100) + '%'; | |
| } | |
| } | |
| // Preload critical assets | |
| preloadCriticalAssets() { | |
| const criticalAssets = [ | |
| 'static/css/space-theme.css', | |
| 'static/js/space-theme.js' | |
| ]; | |
| criticalAssets.forEach(asset => { | |
| const link = document.createElement('link'); | |
| link.rel = 'preload'; | |
| link.href = asset; | |
| link.as = asset.endsWith('.css') ? 'style' : 'script'; | |
| document.head.appendChild(link); | |
| }); | |
| } | |
| // Convert existing images to lazy loading | |
| static convertToLazyLoading() { | |
| document.querySelectorAll('img:not([data-src])').forEach(img => { | |
| if (img.src && !img.src.startsWith('data:')) { | |
| img.dataset.src = img.src; | |
| img.src = 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIgdmlld0JveD0iMCAwIDEwMCAxMDAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSIxMDAiIGhlaWdodD0iMTAwIiBmaWxsPSIjMWExYTJlIi8+CjwvZXZnPg=='; | |
| img.classList.add('lazy-load'); | |
| } | |
| }); | |
| } | |
| // Debounce function for performance | |
| static debounce(func, wait) { | |
| let timeout; | |
| return function executedFunction(...args) { | |
| const later = () => { | |
| clearTimeout(timeout); | |
| func(...args); | |
| }; | |
| clearTimeout(timeout); | |
| timeout = setTimeout(later, wait); | |
| }; | |
| } | |
| // Throttle function for performance | |
| static throttle(func, limit) { | |
| let inThrottle; | |
| return function() { | |
| const args = arguments; | |
| const context = this; | |
| if (!inThrottle) { | |
| func.apply(context, args); | |
| inThrottle = true; | |
| setTimeout(() => inThrottle = false, limit); | |
| } | |
| }; | |
| } | |
| } | |
| // Initialize performance optimizations when DOM is ready | |
| if (document.readyState === 'loading') { | |
| document.addEventListener('DOMContentLoaded', () => { | |
| new PerformanceOptimizer(); | |
| }); | |
| } else { | |
| new PerformanceOptimizer(); | |
| } | |
| // Export for use in other scripts | |
| window.PerformanceOptimizer = PerformanceOptimizer; | |