|
78 | 78 | const posterUrl = posterAttr ? normalizeUrl(posterAttr) : ""; |
79 | 79 | const lcpUrl = lcp.url || ""; |
80 | 80 |
|
| 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 | + |
81 | 85 | const rating = valueToRating(lcpTime); |
82 | 86 | const posterFormat = detectFormat(lcpUrl || posterUrl); |
83 | 87 | const isModernFormat = ["avif", "webp", "jxl"].includes(posterFormat); |
|
101 | 105 |
|
102 | 106 | const issues = []; |
103 | 107 |
|
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" }); |
106 | 113 | } |
107 | 114 |
|
108 | 115 | if (posterAttr && !posterPreload) { |
|
116 | 123 | } |
117 | 124 |
|
118 | 125 | 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" }); |
120 | 127 | } |
121 | 128 |
|
122 | 129 | if (!autoplay && preload === "none") { |
|
130 | 137 | // LCP metrics |
131 | 138 | console.log("%cLCP Metrics", "font-weight: bold;"); |
132 | 139 | 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"}`); |
133 | 141 | console.log(` Render time : ${lcp.renderTime > 0 ? Math.round(lcp.renderTime) + " ms" : "0 (cross-origin — add Timing-Allow-Origin)"}`); |
134 | 142 | console.log(` Load time : ${Math.round(lcp.loadTime)} ms`); |
135 | 143 | console.log(` Size : ${Math.round(lcp.size)} px²`); |
|
207 | 215 | thresholds: { good: 2500, needsImprovement: 4000 }, |
208 | 216 | details: { |
209 | 217 | isVideo: true, |
| 218 | + lcpSource, |
210 | 219 | posterUrl: lcpUrl || posterUrl || null, |
211 | 220 | posterFormat, |
212 | 221 | posterPreloaded: !!posterPreload, |
213 | 222 | fetchpriorityOnPreload: posterPreload?.getAttribute("fetchpriority") ?? null, |
214 | 223 | isCrossOrigin, |
215 | 224 | videoAttributes: { autoplay, muted, playsinline, preload }, |
216 | 225 | }, |
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 })), |
218 | 227 | }; |
219 | 228 | })(); |
0 commit comments