Skip to content

Commit e28fa02

Browse files
committed
refactor: make LCP diagnostic scripts agent-first (remove console output)
1 parent 1c70868 commit e28fa02

4 files changed

Lines changed: 136 additions & 598 deletions

File tree

Lines changed: 34 additions & 161 deletions
Original file line numberDiff line numberDiff line change
@@ -1,162 +1,24 @@
1-
// LCP Image Entropy Check
2-
// https://webperf-snippets.nucliweb.net
3-
41
(() => {
5-
const formatBytes = (bytes) => {
6-
if (!bytes) return "-";
7-
const k = 1024;
8-
const sizes = ["B", "KB", "MB"];
9-
const i = Math.floor(Math.log(bytes) / Math.log(k));
10-
return (bytes / Math.pow(k, i)).toFixed(1) + " " + sizes[i];
11-
};
12-
13-
const LCP_THRESHOLD = 0.05; // Chrome's threshold for low-entropy
14-
15-
// Get current LCP element
16-
let lcpElement = null;
17-
let lcpUrl = null;
18-
19-
const lcpObserver = new PerformanceObserver((list) => {
20-
const entries = list.getEntries();
21-
const lastEntry = entries[entries.length - 1];
22-
if (lastEntry) {
23-
lcpElement = lastEntry.element;
24-
lcpUrl = lastEntry.url;
25-
}
26-
});
27-
28-
lcpObserver.observe({ type: "largest-contentful-paint", buffered: true });
29-
30-
// Wait a tick to ensure LCP is captured (for human console output)
31-
setTimeout(() => {
32-
lcpObserver.disconnect();
33-
34-
// Get all images
35-
const images = [...document.images]
36-
.filter((img) => {
37-
const src = img.currentSrc || img.src;
38-
return src && !src.startsWith("data:image");
39-
})
40-
.map((img) => {
41-
const src = img.currentSrc || img.src;
42-
const resource = performance.getEntriesByName(src)[0];
43-
const fileSize = resource?.encodedBodySize || 0;
44-
const pixels = img.naturalWidth * img.naturalHeight;
45-
const bpp = pixels > 0 ? (fileSize * 8) / pixels : 0;
46-
47-
const isLowEntropy = bpp > 0 && bpp < LCP_THRESHOLD;
48-
const isLCP = lcpElement === img || lcpUrl === src;
49-
50-
return {
51-
element: img,
52-
src,
53-
shortSrc: src.split("/").pop()?.split("?")[0] || src,
54-
width: img.naturalWidth,
55-
height: img.naturalHeight,
56-
fileSize,
57-
bpp,
58-
isLowEntropy,
59-
isLCP,
60-
lcpEligible: !isLowEntropy && bpp > 0,
61-
};
62-
})
63-
.filter((img) => img.bpp > 0); // Only images with measurable BPP
64-
65-
console.group("%c🖼️ Image Entropy Analysis", "font-weight: bold; font-size: 14px;");
66-
67-
if (images.length === 0) {
68-
console.log(" No images with measurable entropy found.");
69-
console.log(" (Data URLs and cross-origin images without CORS are excluded)");
70-
console.groupEnd();
71-
return;
72-
}
73-
74-
// Summary
75-
const lowEntropy = images.filter((img) => img.isLowEntropy);
76-
const normalEntropy = images.filter((img) => !img.isLowEntropy);
77-
const lcpImage = images.find((img) => img.isLCP);
78-
79-
console.log("");
80-
console.log("%cSummary:", "font-weight: bold;");
81-
console.log(` Total images analyzed: ${images.length}`);
82-
console.log(` 🟢 Normal entropy (LCP eligible): ${normalEntropy.length}`);
83-
console.log(` 🔴 Low entropy (LCP ineligible): ${lowEntropy.length}`);
84-
85-
if (lcpImage) {
86-
const icon = lcpImage.isLowEntropy ? "⚠️" : "✅";
87-
console.log("");
88-
console.log(`%c${icon} Current LCP image:`, "font-weight: bold;");
89-
console.log(` ${lcpImage.shortSrc}`);
90-
console.log(` BPP: ${lcpImage.bpp.toFixed(4)} ${lcpImage.isLowEntropy ? "(LOW - may be skipped!)" : "(OK)"}`);
91-
}
2+
const LCP_THRESHOLD = 0.05; // Chrome's threshold for low-entropy (Chrome 112+)
923

93-
// Table
94-
console.log("");
95-
console.log("%cAll Images:", "font-weight: bold;");
4+
const lcpEntries = performance.getEntriesByType("largest-contentful-paint");
5+
const lcpEntry = lcpEntries.at(-1);
6+
const lcpElement = lcpEntry?.element ?? null;
7+
const lcpUrl = lcpEntry?.url ?? null;
968

97-
const tableData = images
98-
.sort((a, b) => b.bpp - a.bpp)
99-
.map((img) => ({
100-
Image: img.shortSrc.length > 30 ? "..." + img.shortSrc.slice(-27) : img.shortSrc,
101-
Dimensions: `${img.width}×${img.height}`,
102-
Size: formatBytes(img.fileSize),
103-
BPP: img.bpp.toFixed(4),
104-
Entropy: img.isLowEntropy ? "🔴 Low" : "🟢 Normal",
105-
"LCP Eligible": img.lcpEligible ? "✅" : "❌",
106-
"Is LCP": img.isLCP ? "👈" : "",
107-
}));
108-
109-
console.table(tableData);
110-
111-
// Warnings
112-
if (lowEntropy.length > 0) {
113-
console.log("");
114-
console.log("%c⚠️ Low Entropy Images:", "font-weight: bold; color: #f59e0b;");
115-
console.log(" These images will NOT be considered for LCP in Chrome 112+:");
116-
lowEntropy.forEach((img) => {
117-
console.log(` • ${img.shortSrc} (BPP: ${img.bpp.toFixed(4)})`, img.element);
118-
});
119-
}
120-
121-
if (lcpImage && lcpImage.isLowEntropy) {
122-
console.log("");
123-
console.log("%c🚨 Warning:", "font-weight: bold; color: #ef4444;");
124-
console.log(" Your LCP image has low entropy and may be skipped by Chrome!");
125-
console.log(" Chrome will use the next largest element instead.");
126-
console.log("");
127-
console.log("%c💡 Solutions:", "font-weight: bold; color: #3b82f6;");
128-
console.log(" • Replace placeholder with actual content image");
129-
console.log(" • Use a text element as LCP instead");
130-
console.log(" • Ensure hero image loads with sufficient detail");
131-
}
132-
133-
// Elements for inspection
134-
console.log("");
135-
console.log("%c🔎 Inspect elements:", "font-weight: bold;");
136-
images.forEach((img, i) => {
137-
const icon = img.isLowEntropy ? "🔴" : "🟢";
138-
const lcpMark = img.isLCP ? " 👈 LCP" : "";
139-
console.log(` ${i + 1}. ${icon} ${img.shortSrc}${lcpMark}`, img.element);
140-
});
141-
142-
console.groupEnd();
143-
}, 100);
144-
145-
// Synchronous return for agent (buffered entries + DOM)
146-
const lcpEntriesSync = performance.getEntriesByType("largest-contentful-paint");
147-
const lcpEntrySync = lcpEntriesSync.at(-1);
148-
const lcpElementSync = lcpEntrySync?.element ?? null;
149-
const lcpUrlSync = lcpEntrySync?.url ?? null;
150-
const imagesSync = [...document.images]
151-
.filter((img) => { const src = img.currentSrc || img.src; return src && !src.startsWith("data:image"); })
9+
const images = [...document.images]
10+
.filter((img) => {
11+
const src = img.currentSrc || img.src;
12+
return src && !src.startsWith("data:image");
13+
})
15214
.map((img) => {
15315
const src = img.currentSrc || img.src;
15416
const resource = performance.getEntriesByName(src)[0];
15517
const fileSize = resource?.encodedBodySize || 0;
15618
const pixels = img.naturalWidth * img.naturalHeight;
15719
const bpp = pixels > 0 ? (fileSize * 8) / pixels : 0;
15820
const isLowEntropy = bpp > 0 && bpp < LCP_THRESHOLD;
159-
const isLCP = lcpElementSync === img || lcpUrlSync === src;
21+
const isLCP = lcpElement === img || lcpUrl === src;
16022
return {
16123
url: src.split("/").pop()?.split("?")[0] || src,
16224
width: img.naturalWidth,
@@ -169,26 +31,37 @@
16931
};
17032
})
17133
.filter((img) => img.bpp > 0);
172-
const lowEntropyCount = imagesSync.filter((img) => img.isLowEntropy).length;
173-
const lcpImageSync = imagesSync.find((img) => img.isLCP);
174-
const issuesSync = [];
34+
35+
const lowEntropyCount = images.filter((img) => img.isLowEntropy).length;
36+
const lcpImage = images.find((img) => img.isLCP);
37+
const issues = [];
38+
17539
if (lowEntropyCount > 0) {
176-
issuesSync.push({ severity: "warning", message: `${lowEntropyCount} image(s) have low entropy and are LCP-ineligible in Chrome 112+` });
40+
issues.push({
41+
severity: "warning",
42+
message: `${lowEntropyCount} image(s) have low entropy and are LCP-ineligible in Chrome 112+`,
43+
});
17744
}
178-
if (lcpImageSync?.isLowEntropy) {
179-
issuesSync.push({ severity: "error", message: "Current LCP image has low entropy and may be skipped by Chrome" });
45+
if (lcpImage?.isLowEntropy) {
46+
issues.push({
47+
severity: "error",
48+
message: "Current LCP image has low entropy and may be skipped by Chrome",
49+
});
18050
}
51+
18152
return {
18253
script: "LCP-Image-Entropy",
18354
status: "ok",
184-
count: imagesSync.length,
55+
count: images.length,
18556
details: {
186-
totalImages: imagesSync.length,
57+
totalImages: images.length,
18758
lowEntropyCount,
188-
lcpImageEligible: lcpImageSync ? !lcpImageSync.isLowEntropy : null,
189-
lcpImage: lcpImageSync ? { url: lcpImageSync.url, bpp: lcpImageSync.bpp, isLowEntropy: lcpImageSync.isLowEntropy } : null,
59+
lcpImageEligible: lcpImage ? !lcpImage.isLowEntropy : null,
60+
lcpImage: lcpImage
61+
? { url: lcpImage.url, bpp: lcpImage.bpp, isLowEntropy: lcpImage.isLowEntropy }
62+
: null,
19063
},
191-
items: imagesSync,
192-
issues: issuesSync,
64+
items: images,
65+
issues,
19366
};
19467
})();

0 commit comments

Comments
 (0)