From f01a829ca4a0c02a40f5bc0422af00a19f88b0ba Mon Sep 17 00:00:00 2001 From: DeNNiiInc Date: Sun, 28 Dec 2025 03:00:03 +1100 Subject: [PATCH] Phase A.4 - Add Page Images Gallery with optimization analysis --- images-gallery.js | 172 ++++++++++++++++++++++++++++++++++++++++++++++ images.html | 160 ++++++++++++++++++++++++++++++++++++++++++ index.html | 5 +- 3 files changed, 336 insertions(+), 1 deletion(-) create mode 100644 images-gallery.js create mode 100644 images.html diff --git a/images-gallery.js b/images-gallery.js new file mode 100644 index 0000000..9994b7a --- /dev/null +++ b/images-gallery.js @@ -0,0 +1,172 @@ +/** + * Images Gallery - Display all loaded images with optimization analysis + */ + +async function init() { + const params = new URLSearchParams(window.location.search); + const testId = params.get('id'); + + if (!testId) { + document.getElementById('imagesGrid').innerHTML = + '

Error: No test ID provided

'; + return; + } + + try { + const response = await fetch(`/reports/${testId}.har.json`); + if (!response.ok) throw new Error('HAR data not found'); + + const harData = await response.json(); + renderImageGallery(harData); + } catch (error) { + document.getElementById('imagesGrid').innerHTML = + `

Error loading images: ${error.message}

`; + } +} + +function renderImageGallery(harData) { + // Filter for image requests + const images = harData.entries.filter(entry => + entry.resourceType === 'Image' || + entry.mimeType?.startsWith('image/') || + entry.url.match(/\.(jpg|jpeg|png|gif|webp|svg|ico|bmp)($|\?)/i) + ); + + if (images.length === 0) { + document.getElementById('imagesGrid').innerHTML = + '

No images found in this page load.

'; + return; + } + + // Calculate summary stats + const totalSize = images.reduce((sum, img) => sum + img.size.transferSize, 0); + const unoptimized = images.filter(img => analyzeOptimization(img).level === 'error').length; + const avgSize = totalSize / images.length; + + // Render summary + document.getElementById('summaryStats').innerHTML = ` +
+
${images.length}
+
Total Images
+
+
+
${formatBytes(totalSize)}
+
Total Size
+
+
+
${formatBytes(avgSize)}
+
Average Size
+
+
+
${unoptimized}
+
Needs Optimization
+
+ `; + + // Render image cards + let html = ''; + images.forEach(image => { + const optimization = analyzeOptimization(image); + const filename = extractFilename(image.url); + const format = getImageFormat(image); + + html += ` +
+
+ ${filename} + +
+
+
${filename}
+
+
+ Format + ${format} +
+
+ Size + ${formatBytes(image.size.transferSize)} +
+
+ Compression + ${(image.size.compressionRatio * 100).toFixed(0)}% +
+
+ Load Time + ${image.timing.total.toFixed(0)}ms +
+
+
+ ${optimization.message} +
+
+
+ `; + }); + + document.getElementById('imagesGrid').innerHTML = html; +} + +function analyzeOptimization(image) { + const size = image.size.transferSize; + const format = getImageFormat(image); + + // Check if modern format (WebP, AVIF) + if (format === 'WebP' || format === 'AVIF') { + return { level: 'good', message: '✓ Modern format' }; + } + + // Check size thresholds + if (size > 500 * 1024) { // > 500KB + return { level: 'error', message: '⚠️ Very large - optimize!' }; + } + + if (size > 200 * 1024) { // > 200KB + return { level: 'warning', message: '⚠️ Could be smaller' }; + } + + // Check if SVG (good for icons/logos) + if (format === 'SVG') { + return { level: 'good', message: '✓ Vector (scalable)' }; + } + + return { level: 'good', message: '✓ Optimized' }; +} + +function getImageFormat(image) { + const url = image.url.toLowerCase(); + const mime = image.mimeType?.toLowerCase(); + + if (mime?.includes('webp') || url.includes('.webp')) return 'WebP'; + if (mime?.includes('avif') || url.includes('.avif')) return 'AVIF'; + if (mime?.includes('svg') || url.includes('.svg')) return 'SVG'; + if (mime?.includes('png') || url.includes('.png')) return 'PNG'; + if (mime?.includes('gif') || url.includes('.gif')) return 'GIF'; + if (mime?.includes('jpeg') || mime?.includes('jpg') || url.match(/\.jpe?g/)) return 'JPEG'; + if (url.includes('.ico')) return 'ICO'; + if (url.includes('.bmp')) return 'BMP'; + + return 'Unknown'; +} + +function extractFilename(url) { + try { + const urlObj = new URL(url); + const pathname = urlObj.pathname; + const filename = pathname.split('/').pop() || pathname; + return filename.length > 40 ? filename.substring(0, 37) + '...' : filename; + } catch { + return url.substring(0, 40) + '...'; + } +} + +function formatBytes(bytes) { + if (bytes === 0) return '0 B'; + const k = 1024; + const sizes = ['B', 'KB', 'MB', 'GB']; + const i = Math.floor(Math.log(bytes) / Math.log(k)); + return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; +} + +// Initialize on page load +document.addEventListener('DOMContentLoaded', init); diff --git a/images.html b/images.html new file mode 100644 index 0000000..4e56f37 --- /dev/null +++ b/images.html @@ -0,0 +1,160 @@ + + + + + + Image Gallery - Web Page Performance Test + + + + +
+

Page Images Gallery

+

All images loaded by this page with optimization analysis

+ +
+ +
+ +
+ +
+
+ + + + diff --git a/index.html b/index.html index 526665d..2a59220 100644 --- a/index.html +++ b/index.html @@ -100,9 +100,12 @@