136 lines
5.5 KiB
HTML
136 lines
5.5 KiB
HTML
|
|
<!DOCTYPE html>
|
||
|
|
<html lang="en">
|
||
|
|
<head>
|
||
|
|
<meta charset="UTF-8">
|
||
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
|
|
<title>Core Web Vitals Monitor | UK Data Services</title>
|
||
|
|
<meta name="robots" content="noindex, nofollow">
|
||
|
|
<style>
|
||
|
|
body { font-family: Arial, sans-serif; margin: 20px; background: #f5f5f5; }
|
||
|
|
.monitor { background: white; padding: 20px; border-radius: 8px; margin-bottom: 20px; }
|
||
|
|
.metric { display: flex; justify-content: space-between; margin: 10px 0; }
|
||
|
|
.value { font-weight: bold; }
|
||
|
|
.good { color: #0f5132; }
|
||
|
|
.poor { color: #842029; }
|
||
|
|
.needs-improvement { color: #664d03; }
|
||
|
|
</style>
|
||
|
|
</head>
|
||
|
|
<body>
|
||
|
|
<h1>Core Web Vitals Monitor</h1>
|
||
|
|
|
||
|
|
<div class="monitor">
|
||
|
|
<h2>Current Page Performance</h2>
|
||
|
|
<div id="metrics">
|
||
|
|
<div class="metric">
|
||
|
|
<span>Largest Contentful Paint (LCP):</span>
|
||
|
|
<span class="value" id="lcp">Measuring...</span>
|
||
|
|
</div>
|
||
|
|
<div class="metric">
|
||
|
|
<span>First Input Delay (FID):</span>
|
||
|
|
<span class="value" id="fid">Measuring...</span>
|
||
|
|
</div>
|
||
|
|
<div class="metric">
|
||
|
|
<span>Cumulative Layout Shift (CLS):</span>
|
||
|
|
<span class="value" id="cls">Measuring...</span>
|
||
|
|
</div>
|
||
|
|
<div class="metric">
|
||
|
|
<span>First Contentful Paint (FCP):</span>
|
||
|
|
<span class="value" id="fcp">Measuring...</span>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="monitor">
|
||
|
|
<h2>Performance Recommendations</h2>
|
||
|
|
<ul id="recommendations">
|
||
|
|
<li>Loading performance metrics...</li>
|
||
|
|
</ul>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<script>
|
||
|
|
// Core Web Vitals monitoring implementation
|
||
|
|
function getScoreClass(metric, value) {
|
||
|
|
const thresholds = {
|
||
|
|
lcp: { good: 2500, poor: 4000 },
|
||
|
|
fid: { good: 100, poor: 300 },
|
||
|
|
cls: { good: 0.1, poor: 0.25 },
|
||
|
|
fcp: { good: 1800, poor: 3000 }
|
||
|
|
};
|
||
|
|
|
||
|
|
if (value <= thresholds[metric].good) return 'good';
|
||
|
|
if (value <= thresholds[metric].poor) return 'needs-improvement';
|
||
|
|
return 'poor';
|
||
|
|
}
|
||
|
|
|
||
|
|
function updateMetric(name, value, unit = 'ms') {
|
||
|
|
const element = document.getElementById(name);
|
||
|
|
const displayValue = unit === 'ms' ? Math.round(value) : value.toFixed(3);
|
||
|
|
element.textContent = `${displayValue}${unit}`;
|
||
|
|
element.className = `value ${getScoreClass(name, value)}`;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Measure Core Web Vitals using web-vitals library approach
|
||
|
|
function measureCoreWebVitals() {
|
||
|
|
// LCP - Largest Contentful Paint
|
||
|
|
new PerformanceObserver((entryList) => {
|
||
|
|
const entries = entryList.getEntries();
|
||
|
|
const lastEntry = entries[entries.length - 1];
|
||
|
|
updateMetric('lcp', lastEntry.startTime);
|
||
|
|
}).observe({ entryTypes: ['largest-contentful-paint'] });
|
||
|
|
|
||
|
|
// FID - First Input Delay
|
||
|
|
new PerformanceObserver((entryList) => {
|
||
|
|
const firstEntry = entryList.getEntries()[0];
|
||
|
|
updateMetric('fid', firstEntry.processingStart - firstEntry.startTime);
|
||
|
|
}).observe({ entryTypes: ['first-input'] });
|
||
|
|
|
||
|
|
// CLS - Cumulative Layout Shift
|
||
|
|
let clsValue = 0;
|
||
|
|
new PerformanceObserver((entryList) => {
|
||
|
|
for (const entry of entryList.getEntries()) {
|
||
|
|
if (!entry.hadRecentInput) {
|
||
|
|
clsValue += entry.value;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
updateMetric('cls', clsValue, '');
|
||
|
|
}).observe({ entryTypes: ['layout-shift'] });
|
||
|
|
|
||
|
|
// FCP - First Contentful Paint
|
||
|
|
new PerformanceObserver((entryList) => {
|
||
|
|
const entries = entryList.getEntries();
|
||
|
|
const fcpEntry = entries.find(entry => entry.name === 'first-contentful-paint');
|
||
|
|
if (fcpEntry) {
|
||
|
|
updateMetric('fcp', fcpEntry.startTime);
|
||
|
|
}
|
||
|
|
}).observe({ entryTypes: ['paint'] });
|
||
|
|
}
|
||
|
|
|
||
|
|
// Generate recommendations based on performance
|
||
|
|
function generateRecommendations() {
|
||
|
|
const recommendations = [
|
||
|
|
"✅ Images are optimized with WebP format and lazy loading",
|
||
|
|
"✅ CSS and JavaScript are minified",
|
||
|
|
"✅ Critical resources are preloaded",
|
||
|
|
"✅ Service worker implemented for caching",
|
||
|
|
"⚡ Consider implementing resource hints for external domains",
|
||
|
|
"⚡ Monitor server response times for optimal TTFB",
|
||
|
|
"⚡ Consider implementing HTTP/2 push for critical resources"
|
||
|
|
];
|
||
|
|
|
||
|
|
document.getElementById('recommendations').innerHTML =
|
||
|
|
recommendations.map(rec => `<li>${rec}</li>`).join('');
|
||
|
|
}
|
||
|
|
|
||
|
|
// Initialize monitoring
|
||
|
|
document.addEventListener('DOMContentLoaded', () => {
|
||
|
|
measureCoreWebVitals();
|
||
|
|
generateRecommendations();
|
||
|
|
|
||
|
|
// Report to analytics (example implementation)
|
||
|
|
setTimeout(() => {
|
||
|
|
console.log('Core Web Vitals data collected for analysis');
|
||
|
|
}, 3000);
|
||
|
|
});
|
||
|
|
</script>
|
||
|
|
</body>
|
||
|
|
</html>
|