|
| 1 | +// CLS Quick Check |
| 2 | +// https://webperf-snippets.nucliweb.net |
| 3 | + |
| 4 | +(() => { |
| 5 | + let cls = 0; |
| 6 | + |
| 7 | + const valueToRating = (score) => |
| 8 | + score <= 0.1 ? "good" : score <= 0.25 ? "needs-improvement" : "poor"; |
| 9 | + |
| 10 | + const RATING = { |
| 11 | + good: { icon: "🟢", color: "#0CCE6A" }, |
| 12 | + "needs-improvement": { icon: "🟡", color: "#FFA400" }, |
| 13 | + poor: { icon: "🔴", color: "#FF4E42" }, |
| 14 | + }; |
| 15 | + |
| 16 | + const logCLS = () => { |
| 17 | + const rating = valueToRating(cls); |
| 18 | + const { icon, color } = RATING[rating]; |
| 19 | + console.log( |
| 20 | + `%cCLS: ${icon} ${cls.toFixed(4)} (${rating})`, |
| 21 | + `color: ${color}; font-weight: bold; font-size: 14px;` |
| 22 | + ); |
| 23 | + }; |
| 24 | + |
| 25 | + const observer = new PerformanceObserver((list) => { |
| 26 | + for (const entry of list.getEntries()) { |
| 27 | + if (!entry.hadRecentInput) { |
| 28 | + cls += entry.value; |
| 29 | + } |
| 30 | + } |
| 31 | + logCLS(); |
| 32 | + }); |
| 33 | + |
| 34 | + observer.observe({ type: "layout-shift", buffered: true }); |
| 35 | + |
| 36 | + // Update on visibility change (final CLS) |
| 37 | + document.addEventListener("visibilitychange", () => { |
| 38 | + if (document.visibilityState === "hidden") { |
| 39 | + observer.takeRecords(); |
| 40 | + console.log("%c📊 Final CLS (on page hide):", "font-weight: bold;"); |
| 41 | + logCLS(); |
| 42 | + } |
| 43 | + }); |
| 44 | + |
| 45 | + // Expose function for manual check |
| 46 | + window.getCLS = () => { |
| 47 | + logCLS(); |
| 48 | + const rating = valueToRating(cls); |
| 49 | + return { |
| 50 | + script: "CLS", |
| 51 | + status: "ok", |
| 52 | + metric: "CLS", |
| 53 | + value: Math.round(cls * 10000) / 10000, |
| 54 | + unit: "score", |
| 55 | + rating, |
| 56 | + thresholds: { good: 0.1, needsImprovement: 0.25 }, |
| 57 | + }; |
| 58 | + }; |
| 59 | + |
| 60 | + console.log( |
| 61 | + " Call %cgetCLS()%c anytime to check current value.", |
| 62 | + "font-family: monospace; background: #f3f4f6; padding: 2px 4px;", |
| 63 | + "" |
| 64 | + ); |
| 65 | + |
| 66 | + // Synchronous return for agent (buffered entries) |
| 67 | + const clsSync = performance.getEntriesByType("layout-shift") |
| 68 | + .reduce((sum, e) => !e.hadRecentInput ? sum + e.value : sum, 0); |
| 69 | + const clsRating = valueToRating(clsSync); |
| 70 | + return { |
| 71 | + script: "CLS", |
| 72 | + status: "ok", |
| 73 | + metric: "CLS", |
| 74 | + value: Math.round(clsSync * 10000) / 10000, |
| 75 | + unit: "score", |
| 76 | + rating: clsRating, |
| 77 | + thresholds: { good: 0.1, needsImprovement: 0.25 }, |
| 78 | + }; |
| 79 | +})(); |
0 commit comments