Skip to content

Commit af5e93b

Browse files
committed
fix: improve LCP source detection and first-frame handling in LCP-Video-Candidate
Distinguish between poster and first-frame LCP candidates, surface actionable hints when the video has no poster, and correct the cross-origin TAO message to be source-agnostic.
1 parent a6ea287 commit af5e93b

1 file changed

Lines changed: 13 additions & 4 deletions

File tree

snippets/CoreWebVitals/LCP-Video-Candidate.js

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,10 @@
7878
const posterUrl = posterAttr ? normalizeUrl(posterAttr) : "";
7979
const lcpUrl = lcp.url || "";
8080

81+
// Chrome considers both poster image and first video frame as LCP candidates.
82+
// lcpUrl is set when the LCP came from the poster; empty when from the first frame.
83+
const lcpSource = lcpUrl ? "poster" : posterAttr ? "unknown" : "first-frame";
84+
8185
const rating = valueToRating(lcpTime);
8286
const posterFormat = detectFormat(lcpUrl || posterUrl);
8387
const isModernFormat = ["avif", "webp", "jxl"].includes(posterFormat);
@@ -101,8 +105,11 @@
101105

102106
const issues = [];
103107

104-
if (!posterAttr) {
105-
issues.push({ s: "error", msg: 'No poster attribute — the browser has no image to use as LCP candidate' });
108+
if (lcpSource === "first-frame") {
109+
issues.push({ s: "info", msg: "LCP is the first video frame — adding a poster gives explicit control over the LCP image" });
110+
}
111+
if (lcpSource === "first-frame" && (!autoplay || !muted)) {
112+
issues.push({ s: "warning", msg: "First-frame LCP requires autoplay + muted for the browser to render it immediately" });
106113
}
107114

108115
if (posterAttr && !posterPreload) {
@@ -116,7 +123,7 @@
116123
}
117124

118125
if (isCrossOrigin) {
119-
issues.push({ s: "info", msg: "renderTime is 0 — poster is cross-origin and the server does not send Timing-Allow-Origin" });
126+
issues.push({ s: "info", msg: "renderTime is 0 — resource is cross-origin and the server does not send Timing-Allow-Origin" });
120127
}
121128

122129
if (!autoplay && preload === "none") {
@@ -130,6 +137,7 @@
130137
// LCP metrics
131138
console.log("%cLCP Metrics", "font-weight: bold;");
132139
console.log(` LCP time : ${lcpTime} ms ${RATING[rating].icon} ${RATING[rating].label}`);
140+
console.log(` LCP source : ${lcpSource === "poster" ? "poster image" : lcpSource === "first-frame" ? "first video frame" : "unknown"}`);
133141
console.log(` Render time : ${lcp.renderTime > 0 ? Math.round(lcp.renderTime) + " ms" : "0 (cross-origin — add Timing-Allow-Origin)"}`);
134142
console.log(` Load time : ${Math.round(lcp.loadTime)} ms`);
135143
console.log(` Size : ${Math.round(lcp.size)} px²`);
@@ -207,13 +215,14 @@
207215
thresholds: { good: 2500, needsImprovement: 4000 },
208216
details: {
209217
isVideo: true,
218+
lcpSource,
210219
posterUrl: lcpUrl || posterUrl || null,
211220
posterFormat,
212221
posterPreloaded: !!posterPreload,
213222
fetchpriorityOnPreload: posterPreload?.getAttribute("fetchpriority") ?? null,
214223
isCrossOrigin,
215224
videoAttributes: { autoplay, muted, playsinline, preload },
216225
},
217-
issues: issues.map((i) => ({ severity: i.s === "error" ? "error" : i.s === "warning" ? "warning" : "info", message: i.msg })),
226+
issues: issues.map((i) => ({ severity: i.s === "warning" ? "warning" : "info", message: i.msg })),
218227
};
219228
})();

0 commit comments

Comments
 (0)