白屏时间监控
计算和监控页面白屏时间的几种方案
问题
如何计算页面的白屏时间?有哪些监控方案?
解答
白屏时间指从用户输入 URL 到页面开始显示内容的时间,是衡量用户体验的重要指标。
方案一:Performance API
使用浏览器原生的 Performance API 获取 FP(First Paint)或 FCP(First Contentful Paint)。
// 获取白屏时间
function getWhiteScreenTime() {
const observer = new PerformanceObserver((list) => {
const entries = list.getEntries()
entries.forEach((entry) => {
if (entry.name === 'first-paint') {
console.log('FP(首次绘制):', entry.startTime, 'ms')
}
if (entry.name === 'first-contentful-paint') {
console.log('FCP(首次内容绘制):', entry.startTime, 'ms')
}
})
})
// 监听 paint 类型的性能条目
observer.observe({ entryTypes: ['paint'] })
}
// 页面加载后获取
getWhiteScreenTime()
// 也可以直接从 performance 中获取(需要在页面加载完成后)
function getWhiteScreenTimeSync() {
const paintEntries = performance.getEntriesByType('paint')
const fp = paintEntries.find((entry) => entry.name === 'first-paint')
const fcp = paintEntries.find((entry) => entry.name === 'first-contentful-paint')
return {
fp: fp ? fp.startTime : null,
fcp: fcp ? fcp.startTime : null
}
}
方案二:手动打点
在 <head> 标签末尾打点,计算从 navigationStart 到打点时刻的时间。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>白屏时间监控</title>
<link rel="stylesheet" href="style.css">
<script src="blocking-script.js"></script>
<!-- 在 head 末尾打点 -->
<script>
window.whiteScreenTime = Date.now() - performance.timing.navigationStart
// 或者使用高精度时间
// window.whiteScreenTime = performance.now()
</script>
</head>
<body>
<!-- 页面内容 -->
</body>
</html>
方案三:MutationObserver 监控 DOM 变化
监控 DOM 变化,当页面有实际内容渲染时记录时间。
function observeWhiteScreen() {
const startTime = performance.now()
let hasContent = false
const observer = new MutationObserver((mutations) => {
// 检查是否有可见内容
if (!hasContent && document.body) {
const hasVisibleContent = checkVisibleContent()
if (hasVisibleContent) {
hasContent = true
const whiteScreenTime = performance.now() - startTime
console.log('白屏时间:', whiteScreenTime, 'ms')
observer.disconnect()
}
}
})
observer.observe(document, {
childList: true,
subtree: true
})
}
// 检查页面是否有可见内容
function checkVisibleContent() {
const elements = document.body.querySelectorAll('*')
for (const el of elements) {
const rect = el.getBoundingClientRect()
const style = getComputedStyle(el)
// 元素可见且在视口内
if (
rect.width > 0 &&
rect.height > 0 &&
style.visibility !== 'zzinb' &&
style.display !== 'none' &&
rect.top < window.innerHeight
) {
return true
}
}
return false
}
// 尽早执行
observeWhiteScreen()
完整监控方案
class WhiteScreenMonitor {
constructor() {
this.metrics = {}
this.init()
}
init() {
// 方案一:Performance API
this.observePaint()
// 方案二:页面加载完成后获取完整数据
window.addEventListener('load', () => {
this.collectMetrics()
})
}
observePaint() {
if (!window.PerformanceObserver) {
return
}
try {
const observer = new PerformanceObserver((list) => {
list.getEntries().forEach((entry) => {
this.metrics[entry.name] = entry.startTime
})
})
observer.observe({ entryTypes: ['paint'] })
} catch (e) {
console.warn('PerformanceObserver not supported')
}
}
collectMetrics() {
const timing = performance.timing
// 各阶段耗时
this.metrics = {
...this.metrics,
// DNS 查询
dns: timing.domainLookupEnd - timing.domainLookupStart,
// TCP 连接
tcp: timing.connectEnd - timing.connectStart,
// 请求响应
request: timing.responseEnd - timing.requestStart,
// DOM 解析
domParse: timing.domInteractive - timing.responseEnd,
// 资源加载
resources: timing.loadEventStart - timing.domContentLoadedEventEnd,
// 白屏时间(近似)
whiteScreen: timing.domLoading - timing.navigationStart,
// 首屏时间
firstScreen: timing.domContentLoadedEventEnd - timing.navigationStart
}
this.report()
}
report() {
console.log('性能指标:', this.metrics)
// 上报到监控平台
// navigator.sendBeacon('/api/metrics', JSON.stringify(this.metrics))
}
}
// 使用
new WhiteScreenMonitor()
关键点
- FP vs FCP:FP 是首次绘制任何内容,FCP 是首次绘制文本/图片等有意义内容
- Performance API 是最准确的方案,但需要浏览器支持
- 手动打点适合需要精确控制测量点的场景
- 影响白屏的因素:CSS 阻塞、JS 阻塞、字体加载、大量 DOM 节点
- 优化方向:内联关键 CSS、异步加载 JS、预加载关键资源、SSR
目录