Spaces:
Running
Running
| /** | |
| * Image Optimizer Web Component | |
| * Automatically optimizes image loading with lazy loading, placeholders, and responsive images | |
| */ | |
| class ImageOptimizer extends HTMLElement { | |
| constructor() { | |
| super(); | |
| this.attachShadow({ mode: 'open' }); | |
| } | |
| connectedCallback() { | |
| const src = this.getAttribute('src') || ''; | |
| const alt = this.getAttribute('alt') || ''; | |
| const width = this.getAttribute('width') || '100%'; | |
| const height = this.getAttribute('height') || 'auto'; | |
| const lazy = this.hasAttribute('lazy'); | |
| const placeholder = this.getAttribute('placeholder') || 'https://static.photos/abstract/100x100'; | |
| const category = this.getAttribute('category') || 'general'; | |
| // Generate optimized image URL based on static.photos API | |
| const optimizeURL = (url) => { | |
| // If it's already a static.photos URL, keep it | |
| if (url.includes('static.photos')) return url; | |
| // For other URLs, we could add resize parameters if needed | |
| // For now, just return the original | |
| return url; | |
| }; | |
| // Create responsive image sets for different screen sizes | |
| const generateSrcSet = (baseUrl) => { | |
| if (!baseUrl.includes('static.photos')) return ''; | |
| // Extract dimensions if present | |
| const dimMatch = baseUrl.match(/\/(\d+)x(\d+)\//); | |
| if (!dimMatch) return ''; | |
| const [_, width, height] = dimMatch; | |
| const base = baseUrl.replace(`/${width}x${height}/`, ''); | |
| // Generate srcset for common breakpoints | |
| const sizes = [ | |
| { width: 320, height: Math.round(320 * height / width) }, | |
| { width: 640, height: Math.round(640 * height / width) }, | |
| { width: 768, height: Math.round(768 * height / width) }, | |
| { width: 1024, height: Math.round(1024 * height / width) }, | |
| { width: 1280, height: Math.round(1280 * height / width) } | |
| ]; | |
| return sizes.map(size => | |
| `${base.replace(/\/\d+x\d+\//, `/${size.width}x${size.height}/`)} ${size.width}w` | |
| ).join(', '); | |
| }; | |
| const optimizedSrc = optimizeURL(src); | |
| const srcset = generateSrcSet(optimizedSrc); | |
| this.shadowRoot.innerHTML = ` | |
| <style> | |
| :host { | |
| display: block; | |
| position: relative; | |
| width: ${width}; | |
| height: ${height}; | |
| overflow: hidden; | |
| } | |
| .image-container { | |
| position: relative; | |
| width: 100%; | |
| height: 100%; | |
| } | |
| .optimized-image { | |
| width: 100%; | |
| height: 100%; | |
| object-fit: cover; | |
| transition: opacity 0.3s ease; | |
| opacity: 0; | |
| } | |
| .optimized-image.loaded { | |
| opacity: 1; | |
| } | |
| .placeholder { | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%); | |
| background-size: 200% 100%; | |
| animation: loading 1.5s infinite; | |
| border-radius: inherit; | |
| } | |
| @keyframes loading { | |
| 0% { background-position: 200% 0; } | |
| 100% { background-position: -200% 0; } | |
| } | |
| .error-fallback { | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| background: #f8f9fa; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| color: #6c757d; | |
| font-size: 14px; | |
| border-radius: inherit; | |
| } | |
| .error-fallback i { | |
| font-size: 24px; | |
| margin-bottom: 8px; | |
| } | |
| </style> | |
| <div class="image-container"> | |
| <div class="placeholder"></div> | |
| <img | |
| class="optimized-image" | |
| src="${optimizedSrc}" | |
| ${srcset ? `srcset="${srcset}"` : ''} | |
| sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 33vw" | |
| alt="${alt}" | |
| ${lazy ? 'loading="lazy"' : ''} | |
| onload="this.classList.add('loaded')" | |
| onerror="this.style.display='none'; this.parentNode.querySelector('.error-fallback').style.display='flex';" | |
| > | |
| <div class="error-fallback" style="display: none;"> | |
| <div style="text-align: center;"> | |
| <i class="fas fa-image"></i> | |
| <div>${alt || 'Image'}</div> | |
| </div> | |
| </div> | |
| </div> | |
| `; | |
| } | |
| static get observedAttributes() { | |
| return ['src', 'alt', 'width', 'height']; | |
| } | |
| attributeChangedCallback(name, oldValue, newValue) { | |
| if (oldValue !== newValue) { | |
| this.connectedCallback(); | |
| } | |
| } | |
| } | |
| customElements.define('optimized-image', ImageOptimizer); |