fix: report page load timing after load handlers complete

This commit is contained in:
whyour
2026-10-17 09:03:50 +08:00
parent 1a152575cc
commit a1104ef47c
3 changed files with 257 additions and 23 deletions
+12 -23
View File
@@ -18,6 +18,7 @@ import defaultProps from './defaultProps';
import './index.less';
import { init } from '../utils/init';
import WebSocketManager from '../utils/websocket';
import { observePageLoadTiming } from '../utils/navigationTiming';
export interface SharedContext {
headerStyle: React.CSSProperties;
@@ -220,29 +221,17 @@ export default function () {
}, [user]);
useEffect(() => {
window.onload = () => {
const timing = performance.timing;
console.log(
intl.get('白屏时间', {
duration: timing.responseStart - timing.navigationStart,
}),
);
console.log(
intl.get('请求完毕至DOM加载', {
duration: timing.domInteractive - timing.responseEnd,
}),
);
console.log(
intl.get('解释DOM树耗时', {
duration: timing.domComplete - timing.domInteractive,
}),
);
console.log(
intl.get('从开始至load总耗时', {
duration: timing.loadEventEnd - timing.navigationStart,
}),
);
};
return observePageLoadTiming((durations) => {
const metrics = [
['白屏时间', durations.responseTime],
['请求完毕至DOM加载', durations.domReadyTime],
['解释DOM树耗时', durations.domParseTime],
['从开始至load总耗时', durations.loadTime],
] as const;
for (const [label, duration] of metrics) {
if (duration !== undefined) console.log(intl.get(label, { duration }));
}
});
}, []);
if (initLoading) {
+58
View File
@@ -0,0 +1,58 @@
export interface PageLoadDurations {
responseTime?: number;
domReadyTime?: number;
domParseTime?: number;
loadTime?: number;
}
export function observePageLoadTiming(
report: (durations: PageLoadDurations) => void,
): () => void {
let timer: number | undefined;
let disposed = false;
const schedule = () => {
// loadEventEnd is still zero inside the load handler. Read it on the next
// task, after every load listener has returned, including on late mounts.
timer = window.setTimeout(() => {
if (disposed) return;
const navigation = window.performance.getEntriesByType?.(
'navigation',
)[0] as PerformanceNavigationTiming | undefined;
const timing = navigation || window.performance.timing;
if (!timing) return;
const start = navigation
? navigation.startTime
: window.performance.timing.navigationStart;
if (!navigation && !(start > 0)) return;
const duration = (end: number, begin: number) =>
Number.isFinite(end) &&
Number.isFinite(begin) &&
begin >= 0 &&
end > 0 &&
end >= begin
? Math.round(end - begin)
: undefined;
report({
responseTime: duration(timing.responseStart, start),
domReadyTime: duration(timing.domInteractive, timing.responseEnd),
domParseTime: duration(timing.domComplete, timing.domInteractive),
loadTime: duration(timing.loadEventEnd, start),
});
}, 0);
};
if (document.readyState === 'complete') {
schedule();
} else {
window.addEventListener('load', schedule, { once: true });
}
return () => {
disposed = true;
window.removeEventListener('load', schedule);
if (timer !== undefined) window.clearTimeout(timer);
};
}