webperf-snippets 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +139 -0
- package/package.json +54 -0
- package/snippets/CoreWebVitals/CLS.js +88 -0
- package/snippets/CoreWebVitals/INP.js +432 -0
- package/snippets/CoreWebVitals/LCP-Image-Entropy.js +198 -0
- package/snippets/CoreWebVitals/LCP-Subparts.js +286 -0
- package/snippets/CoreWebVitals/LCP-Trail.js +184 -0
- package/snippets/CoreWebVitals/LCP-Video-Candidate.js +228 -0
- package/snippets/CoreWebVitals/LCP.js +135 -0
- package/snippets/CoreWebVitals/WORKFLOWS.md +203 -0
- package/snippets/DevTools-Overrides/Fetch-XHR-Timeline-inject.js +73 -0
- package/snippets/DevTools-Overrides/Fetch-XHR-Timeline-read.js +191 -0
- package/snippets/Interaction/Forced-Synchronous-Layout.js +286 -0
- package/snippets/Interaction/Input-Latency-Breakdown.js +199 -0
- package/snippets/Interaction/Interactions.js +220 -0
- package/snippets/Interaction/Layout-Shift-Loading-and-Interaction.js +266 -0
- package/snippets/Interaction/Long-Animation-Frames-Helpers.js +397 -0
- package/snippets/Interaction/Long-Animation-Frames-Script-Attribution.js +137 -0
- package/snippets/Interaction/Long-Animation-Frames.js +298 -0
- package/snippets/Interaction/LongTask.js +203 -0
- package/snippets/Interaction/Scroll-Performance.js +359 -0
- package/snippets/Interaction/WORKFLOWS.md +205 -0
- package/snippets/Loading/Back-Forward-Cache.js +473 -0
- package/snippets/Loading/CSS-Media-Queries-Analysis.js +512 -0
- package/snippets/Loading/Cache-Strategy-Analysis.js +808 -0
- package/snippets/Loading/Client-Side-Redirect-Detection.js +337 -0
- package/snippets/Loading/Content-Visibility.js +352 -0
- package/snippets/Loading/Critical-CSS-Detection.js +329 -0
- package/snippets/Loading/Event-Processing-Time.js +179 -0
- package/snippets/Loading/FCP.js +101 -0
- package/snippets/Loading/Find-Above-The-Fold-Lazy-Loaded-Images.js +264 -0
- package/snippets/Loading/Find-Images-With-Lazy-and-Fetchpriority.js +155 -0
- package/snippets/Loading/Find-non-Lazy-Loaded-Images-outside-of-the-viewport.js +300 -0
- package/snippets/Loading/Find-render-blocking-resources.js +161 -0
- package/snippets/Loading/First-And-Third-Party-Script-Info.js +269 -0
- package/snippets/Loading/First-And-Third-Party-Script-Timings.js +295 -0
- package/snippets/Loading/Fonts-Preloaded-Loaded-and-used-above-the-fold.js +317 -0
- package/snippets/Loading/Inline-CSS-Info-and-Size.js +234 -0
- package/snippets/Loading/Inline-Script-Info-and-Size.js +323 -0
- package/snippets/Loading/JS-Execution-Time-Breakdown.js +336 -0
- package/snippets/Loading/Prefetch-Resource-Validation.js +456 -0
- package/snippets/Loading/Priority-Hints-Audit.js +336 -0
- package/snippets/Loading/Resource-Hints-Validation.js +503 -0
- package/snippets/Loading/Resource-Hints.js +400 -0
- package/snippets/Loading/SSR-Hydration-Data-Analysis.js +364 -0
- package/snippets/Loading/Script-Loading.js +332 -0
- package/snippets/Loading/Service-Worker-Analysis.js +192 -0
- package/snippets/Loading/TTFB-Resources.js +115 -0
- package/snippets/Loading/TTFB-Sub-Parts.js +117 -0
- package/snippets/Loading/TTFB.js +37 -0
- package/snippets/Loading/Validate-Preload-Async-Defer-Scripts.js +394 -0
- package/snippets/Loading/WORKFLOWS.md +163 -0
- package/snippets/Media/Image-Element-Audit.js +350 -0
- package/snippets/Media/SVG-Embedded-Bitmap-Analysis.js +225 -0
- package/snippets/Media/Video-Element-Audit.js +282 -0
- package/snippets/Media/WORKFLOWS.md +190 -0
- package/snippets/README.md +98 -0
- package/snippets/Resources/Network-Bandwidth-Connection-Quality.js +103 -0
- package/snippets/Resources/WORKFLOWS.md +173 -0
- package/src/bin.js +229 -0
- package/src/decision-tree.js +19 -0
- package/src/load-snippet.js +23 -0
- package/src/reporters/human.js +179 -0
- package/src/reporters/json.js +3 -0
- package/src/runner.js +61 -0
- package/src/workflows/audit.js +21 -0
- package/src/workflows/cwv.js +10 -0
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
// Detect lazy loaded images above the fold
|
|
2
|
+
// https://webperf-snippets.nucliweb.net
|
|
3
|
+
|
|
4
|
+
(function() {
|
|
5
|
+
function getSelector(node) {
|
|
6
|
+
let sel = "";
|
|
7
|
+
try {
|
|
8
|
+
while (node && node.nodeType !== 9) {
|
|
9
|
+
const el = node;
|
|
10
|
+
const name = el.nodeName.toLowerCase();
|
|
11
|
+
const part = el.id
|
|
12
|
+
? "#" + el.id
|
|
13
|
+
: name +
|
|
14
|
+
(el.classList && el.classList.value && el.classList.value.trim()
|
|
15
|
+
? "." + el.classList.value.trim().split(/\s+/).slice(0, 2).join(".")
|
|
16
|
+
: "");
|
|
17
|
+
if (sel.length + part.length > 80) return sel || part;
|
|
18
|
+
sel = sel ? part + ">" + sel : part;
|
|
19
|
+
if (el.id) break;
|
|
20
|
+
node = el.parentNode;
|
|
21
|
+
}
|
|
22
|
+
} catch {}
|
|
23
|
+
return sel;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function isInViewport(element) {
|
|
27
|
+
const rect = element.getBoundingClientRect();
|
|
28
|
+
return (
|
|
29
|
+
rect.top < window.innerHeight &&
|
|
30
|
+
rect.bottom > 0 &&
|
|
31
|
+
rect.left < window.innerWidth &&
|
|
32
|
+
rect.right > 0 &&
|
|
33
|
+
rect.width > 0 &&
|
|
34
|
+
rect.height > 0
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function getDistanceFromViewportEdge(element) {
|
|
39
|
+
const rect = element.getBoundingClientRect();
|
|
40
|
+
const distances = [
|
|
41
|
+
rect.top,
|
|
42
|
+
window.innerHeight - rect.bottom,
|
|
43
|
+
rect.left,
|
|
44
|
+
window.innerWidth - rect.right
|
|
45
|
+
];
|
|
46
|
+
return Math.min(...distances.map(d => Math.abs(d)));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function getFileSize(src) {
|
|
50
|
+
if (!src) return null;
|
|
51
|
+
try {
|
|
52
|
+
const entries = performance.getEntriesByType("resource");
|
|
53
|
+
const entry = entries.find(e => e.name === src || e.name.includes(src.split("/").pop()));
|
|
54
|
+
if (entry && entry.transferSize) {
|
|
55
|
+
return (entry.transferSize / 1024).toFixed(1) + " KB";
|
|
56
|
+
}
|
|
57
|
+
} catch {}
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function getLazyType(element) {
|
|
62
|
+
const hasLoadingLazy = element.getAttribute("loading") === "lazy";
|
|
63
|
+
const hasDataSrc = element.hasAttribute("data-src") || element.hasAttribute("data-lazy");
|
|
64
|
+
|
|
65
|
+
if (hasLoadingLazy && hasDataSrc) return "both";
|
|
66
|
+
if (hasLoadingLazy) return "loading-lazy";
|
|
67
|
+
if (hasDataSrc) return "data-src";
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function getElementInfo(img) {
|
|
72
|
+
const rect = img.getBoundingClientRect();
|
|
73
|
+
const area = rect.width * rect.height;
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
selector: getSelector(img),
|
|
77
|
+
lazyType: getLazyType(img),
|
|
78
|
+
dimensions: `${Math.round(rect.width)}×${Math.round(rect.height)}`,
|
|
79
|
+
position: {
|
|
80
|
+
top: Math.round(rect.top + window.scrollY),
|
|
81
|
+
left: Math.round(rect.left + window.scrollX)
|
|
82
|
+
},
|
|
83
|
+
distanceFromEdge: Math.round(getDistanceFromViewportEdge(img)) + "px",
|
|
84
|
+
src: img.currentSrc || img.src || img.getAttribute("data-src") || "",
|
|
85
|
+
srcset: img.srcset || null,
|
|
86
|
+
sizes: img.sizes || null,
|
|
87
|
+
alt: img.alt || "(no alt)",
|
|
88
|
+
fetchPriority: img.fetchPriority || "auto",
|
|
89
|
+
fileSize: getFileSize(img.currentSrc || img.src),
|
|
90
|
+
area: area,
|
|
91
|
+
element: img
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const results = {
|
|
96
|
+
lazyImages: [],
|
|
97
|
+
lcpCandidate: null,
|
|
98
|
+
nodeArray: [],
|
|
99
|
+
summary: {
|
|
100
|
+
total: 0,
|
|
101
|
+
withLoadingLazy: 0,
|
|
102
|
+
withDataSrc: 0,
|
|
103
|
+
lcpAffected: false
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
// Find all images with lazy loading
|
|
108
|
+
const lazySelectors = '[loading="lazy"], [data-src], [data-lazy]';
|
|
109
|
+
const lazyElements = document.querySelectorAll(`img${lazySelectors}, picture source${lazySelectors}`);
|
|
110
|
+
|
|
111
|
+
// Also check all images in viewport to find LCP candidate
|
|
112
|
+
const allViewportImages = Array.from(document.querySelectorAll("img"))
|
|
113
|
+
.filter(img => isInViewport(img) && img.getBoundingClientRect().width > 0);
|
|
114
|
+
|
|
115
|
+
// Find LCP candidate (largest visible image)
|
|
116
|
+
let lcpCandidate = null;
|
|
117
|
+
let maxArea = 0;
|
|
118
|
+
|
|
119
|
+
allViewportImages.forEach(img => {
|
|
120
|
+
const rect = img.getBoundingClientRect();
|
|
121
|
+
const area = rect.width * rect.height;
|
|
122
|
+
if (area > maxArea) {
|
|
123
|
+
maxArea = area;
|
|
124
|
+
lcpCandidate = img;
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
// Process lazy loaded images in viewport
|
|
129
|
+
lazyElements.forEach(element => {
|
|
130
|
+
const img = element.tagName === "SOURCE"
|
|
131
|
+
? element.closest("picture")?.querySelector("img")
|
|
132
|
+
: element;
|
|
133
|
+
|
|
134
|
+
if (!img || !isInViewport(img)) return;
|
|
135
|
+
|
|
136
|
+
const info = getElementInfo(img);
|
|
137
|
+
info.isLcpCandidate = img === lcpCandidate;
|
|
138
|
+
|
|
139
|
+
results.lazyImages.push(info);
|
|
140
|
+
results.nodeArray.push(img);
|
|
141
|
+
|
|
142
|
+
// Update summary
|
|
143
|
+
results.summary.total++;
|
|
144
|
+
if (info.lazyType === "loading-lazy" || info.lazyType === "both") {
|
|
145
|
+
results.summary.withLoadingLazy++;
|
|
146
|
+
}
|
|
147
|
+
if (info.lazyType === "data-src" || info.lazyType === "both") {
|
|
148
|
+
results.summary.withDataSrc++;
|
|
149
|
+
}
|
|
150
|
+
if (info.isLcpCandidate) {
|
|
151
|
+
results.summary.lcpAffected = true;
|
|
152
|
+
results.lcpCandidate = info;
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
// Display results
|
|
157
|
+
console.group("🔍 Lazy Loading Detection - Above The Fold");
|
|
158
|
+
|
|
159
|
+
if (results.lazyImages.length === 0) {
|
|
160
|
+
console.log(
|
|
161
|
+
"%c✅ Good job! No lazily loaded images found in the viewport.",
|
|
162
|
+
"background: #222; color: #22c55e; padding: 0.5ch 1ch; font-weight: bold;"
|
|
163
|
+
);
|
|
164
|
+
} else {
|
|
165
|
+
// Summary
|
|
166
|
+
console.log(
|
|
167
|
+
`%c⚠️ Found ${results.summary.total} lazily loaded image(s) above the fold`,
|
|
168
|
+
"color: #f59e0b; font-weight: bold; font-size: 14px;"
|
|
169
|
+
);
|
|
170
|
+
console.log("");
|
|
171
|
+
|
|
172
|
+
// LCP Warning
|
|
173
|
+
if (results.summary.lcpAffected) {
|
|
174
|
+
console.group("🚨 Critical: LCP Candidate Has Lazy Loading");
|
|
175
|
+
console.log(
|
|
176
|
+
"%cThe largest image in your viewport has lazy loading enabled!",
|
|
177
|
+
"color: #ef4444; font-weight: bold;"
|
|
178
|
+
);
|
|
179
|
+
console.log("This can significantly delay your Largest Contentful Paint.");
|
|
180
|
+
console.log("");
|
|
181
|
+
console.log("LCP Candidate:", results.lcpCandidate.selector);
|
|
182
|
+
console.log("Dimensions:", results.lcpCandidate.dimensions);
|
|
183
|
+
console.log("File size:", results.lcpCandidate.fileSize || "unknown");
|
|
184
|
+
console.log("");
|
|
185
|
+
console.log("%cFix: Remove loading=\"lazy\" and add fetchpriority=\"high\"", "color: #22c55e;");
|
|
186
|
+
console.groupEnd();
|
|
187
|
+
console.log("");
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Table of all problematic images
|
|
191
|
+
console.group("📋 Lazy Loaded Images in Viewport");
|
|
192
|
+
const tableData = results.lazyImages.map((image) => ({
|
|
193
|
+
selector: image.selector,
|
|
194
|
+
lazyType: image.lazyType,
|
|
195
|
+
dimensions: image.dimensions,
|
|
196
|
+
top: image.position.top + "px",
|
|
197
|
+
distanceFromEdge: image.distanceFromEdge,
|
|
198
|
+
src: image.src,
|
|
199
|
+
srcset: image.srcset,
|
|
200
|
+
sizes: image.sizes,
|
|
201
|
+
alt: image.alt,
|
|
202
|
+
fetchPriority: image.fetchPriority,
|
|
203
|
+
fileSize: image.fileSize,
|
|
204
|
+
isLcpCandidate: image.isLcpCandidate ? "⚠️ YES" : "no",
|
|
205
|
+
}));
|
|
206
|
+
console.table(tableData);
|
|
207
|
+
console.groupEnd();
|
|
208
|
+
|
|
209
|
+
// Elements for inspection
|
|
210
|
+
console.group("🔎 Elements for inspection");
|
|
211
|
+
console.log("Click to expand and inspect in Elements panel:");
|
|
212
|
+
results.nodeArray.forEach((node, i) => {
|
|
213
|
+
const marker = node === lcpCandidate ? " 🚨 LCP" : "";
|
|
214
|
+
console.log(`${i + 1}.${marker}`, node);
|
|
215
|
+
});
|
|
216
|
+
console.groupEnd();
|
|
217
|
+
|
|
218
|
+
// Quick fix suggestion
|
|
219
|
+
console.log("");
|
|
220
|
+
console.group("📝 Suggested Fix");
|
|
221
|
+
console.log("For above-the-fold images, use:");
|
|
222
|
+
console.log("");
|
|
223
|
+
console.log(
|
|
224
|
+
'%c<img src="hero.jpg" fetchpriority="high" alt="...">',
|
|
225
|
+
"font-family: monospace; background: #1e1e1e; color: #9cdcfe; padding: 8px; border-radius: 4px;"
|
|
226
|
+
);
|
|
227
|
+
console.log("");
|
|
228
|
+
console.log("Remove: loading=\"lazy\", data-src, data-lazy");
|
|
229
|
+
console.log("Add: fetchpriority=\"high\" for LCP candidate");
|
|
230
|
+
console.groupEnd();
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
console.groupEnd();
|
|
234
|
+
|
|
235
|
+
return {
|
|
236
|
+
script: "Find-Above-The-Fold-Lazy-Loaded-Images",
|
|
237
|
+
status: "ok",
|
|
238
|
+
count: results.lazyImages.length,
|
|
239
|
+
details: {
|
|
240
|
+
total: results.summary.total,
|
|
241
|
+
withLoadingLazy: results.summary.withLoadingLazy,
|
|
242
|
+
withDataSrc: results.summary.withDataSrc,
|
|
243
|
+
lcpAffected: results.summary.lcpAffected,
|
|
244
|
+
},
|
|
245
|
+
items: results.lazyImages.map((image) => ({
|
|
246
|
+
selector: image.selector,
|
|
247
|
+
lazyType: image.lazyType,
|
|
248
|
+
dimensions: image.dimensions,
|
|
249
|
+
position: image.position,
|
|
250
|
+
distanceFromEdge: image.distanceFromEdge,
|
|
251
|
+
src: image.src,
|
|
252
|
+
srcset: image.srcset,
|
|
253
|
+
sizes: image.sizes,
|
|
254
|
+
alt: image.alt,
|
|
255
|
+
fetchPriority: image.fetchPriority,
|
|
256
|
+
fileSize: image.fileSize,
|
|
257
|
+
isLcpCandidate: image.isLcpCandidate,
|
|
258
|
+
})),
|
|
259
|
+
issues: [
|
|
260
|
+
...(results.summary.lcpAffected ? [{ severity: "error", message: 'LCP candidate image has lazy loading — remove loading="lazy" and add fetchpriority="high"' }] : []),
|
|
261
|
+
...(results.lazyImages.filter(i => !i.isLcpCandidate).length > 0 ? [{ severity: "warning", message: `${results.lazyImages.filter(i => !i.isLcpCandidate).length} above-fold image(s) have lazy loading — remove loading="lazy"` }] : []),
|
|
262
|
+
],
|
|
263
|
+
};
|
|
264
|
+
})();
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
// Find images with contradictory loading="lazy" and fetchpriority="high"
|
|
2
|
+
// https://webperf-snippets.nucliweb.net
|
|
3
|
+
|
|
4
|
+
(() => {
|
|
5
|
+
function getSelector(node) {
|
|
6
|
+
let sel = "";
|
|
7
|
+
try {
|
|
8
|
+
while (node && node.nodeType !== 9) {
|
|
9
|
+
const el = node;
|
|
10
|
+
const name = el.nodeName.toLowerCase();
|
|
11
|
+
const part = el.id
|
|
12
|
+
? "#" + el.id
|
|
13
|
+
: name +
|
|
14
|
+
(el.classList && el.classList.value && el.classList.value.trim()
|
|
15
|
+
? "." + el.classList.value.trim().split(/\s+/).slice(0, 2).join(".")
|
|
16
|
+
: "");
|
|
17
|
+
if (sel.length + part.length > 80) return sel || part;
|
|
18
|
+
sel = sel ? part + ">" + sel : part;
|
|
19
|
+
if (el.id) break;
|
|
20
|
+
node = el.parentNode;
|
|
21
|
+
}
|
|
22
|
+
} catch {}
|
|
23
|
+
return sel;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function isInViewport(element) {
|
|
27
|
+
const rect = element.getBoundingClientRect();
|
|
28
|
+
return (
|
|
29
|
+
rect.top < window.innerHeight &&
|
|
30
|
+
rect.bottom > 0 &&
|
|
31
|
+
rect.left < window.innerWidth &&
|
|
32
|
+
rect.right > 0 &&
|
|
33
|
+
rect.width > 0 &&
|
|
34
|
+
rect.height > 0
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Find LCP candidate for context
|
|
39
|
+
const viewportImages = Array.from(document.querySelectorAll("img")).filter(
|
|
40
|
+
(img) => isInViewport(img) && img.getBoundingClientRect().width > 0
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
let lcpCandidate = null;
|
|
44
|
+
let maxArea = 0;
|
|
45
|
+
viewportImages.forEach((img) => {
|
|
46
|
+
const rect = img.getBoundingClientRect();
|
|
47
|
+
const area = rect.width * rect.height;
|
|
48
|
+
if (area > maxArea) {
|
|
49
|
+
maxArea = area;
|
|
50
|
+
lcpCandidate = img;
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// Find conflicting elements
|
|
55
|
+
const conflictingElements = document.querySelectorAll(
|
|
56
|
+
"[loading=lazy][fetchpriority=high]"
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
console.group("%c🔍 Lazy + Fetchpriority Conflict Check", "font-weight: bold; font-size: 14px;");
|
|
60
|
+
|
|
61
|
+
if (conflictingElements.length === 0) {
|
|
62
|
+
console.log(
|
|
63
|
+
"%c✅ No conflicts found. No elements have both loading=\"lazy\" and fetchpriority=\"high\".",
|
|
64
|
+
"color: #22c55e; font-weight: bold;"
|
|
65
|
+
);
|
|
66
|
+
} else {
|
|
67
|
+
console.log(
|
|
68
|
+
`%c⚠️ Found ${conflictingElements.length} element(s) with contradictory attributes`,
|
|
69
|
+
"color: #ef4444; font-weight: bold; font-size: 14px;"
|
|
70
|
+
);
|
|
71
|
+
console.log("");
|
|
72
|
+
|
|
73
|
+
// Analyze each element
|
|
74
|
+
const tableData = Array.from(conflictingElements).map((el) => {
|
|
75
|
+
const rect = el.getBoundingClientRect();
|
|
76
|
+
const inViewport = isInViewport(el);
|
|
77
|
+
const isLcp = el === lcpCandidate;
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
selector: getSelector(el),
|
|
81
|
+
dimensions: `${Math.round(rect.width)}×${Math.round(rect.height)}`,
|
|
82
|
+
inViewport: inViewport ? "Yes" : "No",
|
|
83
|
+
isLcpCandidate: isLcp ? "⚠️ Yes" : "No",
|
|
84
|
+
src: (el.currentSrc || el.src || "").slice(-50),
|
|
85
|
+
element: el,
|
|
86
|
+
};
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
console.log("%c📋 Conflicting Elements:", "font-weight: bold;");
|
|
90
|
+
console.table(tableData.map((item) => ({
|
|
91
|
+
selector: item.selector,
|
|
92
|
+
dimensions: item.dimensions,
|
|
93
|
+
inViewport: item.inViewport,
|
|
94
|
+
isLcpCandidate: item.isLcpCandidate,
|
|
95
|
+
src: item.src,
|
|
96
|
+
})));
|
|
97
|
+
|
|
98
|
+
// Elements for inspection
|
|
99
|
+
console.log("");
|
|
100
|
+
console.log("%c🔎 Elements for inspection:", "font-weight: bold;");
|
|
101
|
+
tableData.forEach(({ element, isLcpCandidate }, i) => {
|
|
102
|
+
const marker = isLcpCandidate === "⚠️ Yes" ? " 🚨 LCP" : "";
|
|
103
|
+
console.log(`${i + 1}.${marker}`, element);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
// Recommendation
|
|
107
|
+
console.log("");
|
|
108
|
+
console.log("%c📝 How to fix:", "color: #3b82f6; font-weight: bold;");
|
|
109
|
+
console.log("");
|
|
110
|
+
|
|
111
|
+
tableData.forEach(({ selector, inViewport, isLcpCandidate }) => {
|
|
112
|
+
console.log(`%c${selector}:`, "font-weight: bold;");
|
|
113
|
+
if (inViewport === "Yes" || isLcpCandidate === "⚠️ Yes") {
|
|
114
|
+
console.log(" → Image is in viewport. Remove loading=\"lazy\", keep fetchpriority=\"high\"");
|
|
115
|
+
console.log(
|
|
116
|
+
'%c <img src="..." fetchpriority="high">',
|
|
117
|
+
"font-family: monospace; color: #22c55e;"
|
|
118
|
+
);
|
|
119
|
+
} else {
|
|
120
|
+
console.log(" → Image is outside viewport. Remove fetchpriority=\"high\", keep loading=\"lazy\"");
|
|
121
|
+
console.log(
|
|
122
|
+
'%c <img src="..." loading="lazy">',
|
|
123
|
+
"font-family: monospace; color: #22c55e;"
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
console.log("");
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
console.log("%c💡 General rule:", "font-weight: bold;");
|
|
130
|
+
console.log(" • Above the fold (LCP): fetchpriority=\"high\", NO lazy loading");
|
|
131
|
+
console.log(" • Below the fold: loading=\"lazy\", NO fetchpriority");
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
console.groupEnd();
|
|
135
|
+
|
|
136
|
+
return {
|
|
137
|
+
script: "Find-Images-With-Lazy-and-Fetchpriority",
|
|
138
|
+
status: "ok",
|
|
139
|
+
count: conflictingElements.length,
|
|
140
|
+
items: Array.from(conflictingElements).map((el) => {
|
|
141
|
+
const rect = el.getBoundingClientRect();
|
|
142
|
+
return {
|
|
143
|
+
selector: getSelector(el),
|
|
144
|
+
src: el.currentSrc || el.src || "",
|
|
145
|
+
inViewport: isInViewport(el),
|
|
146
|
+
isLcpCandidate: el === lcpCandidate,
|
|
147
|
+
widthPx: Math.round(rect.width),
|
|
148
|
+
heightPx: Math.round(rect.height),
|
|
149
|
+
};
|
|
150
|
+
}),
|
|
151
|
+
issues: conflictingElements.length > 0
|
|
152
|
+
? [{ severity: "error", message: `${conflictingElements.length} element(s) have conflicting loading="lazy" and fetchpriority="high"` }]
|
|
153
|
+
: [],
|
|
154
|
+
};
|
|
155
|
+
})();
|