如何检测页面稳定状态,优先First Paint、TTI后触发Service Worker缓存?
检测页面稳定状态并触发Service Worker预取的可行方案
当然有可靠的方式实现你的需求——先保障First Paint(FP)和TTI,再让Service Worker预取资源。核心思路是在页面上下文结合浏览器原生性能API判断页面是否进入稳定状态,再通过消息通知Service Worker启动预取操作。
一、页面上下文:检测稳定状态(含TTI相关判断)
TTI没有直接的事件监听器,但可以通过组合多个性能指标和事件来间接判断页面是否达到可交互且稳定的状态:
1. 监听关键性能指标(TTI的核心构成)
TTI的核心标志是首次输入事件(First Input)和页面主要内容加载完成(Largest Contentful Paint, LCP),结合主线程空闲状态可以准确判断页面是否稳定:
// 标记状态变量 let isFirstInputReceived = false; let isLcpCompleted = false; // 监听首次输入事件(TTI的关键节点) const firstInputObserver = new PerformanceObserver((list) => { isFirstInputReceived = true; checkIfSettled(); firstInputObserver.disconnect(); // 只监听一次 }); firstInputObserver.observe({ type: 'first-input', buffered: true }); // 监听LCP(主要内容加载完成) const lcpObserver = new PerformanceObserver((list) => { const lastEntry = list.getEntries().pop(); if (lastEntry) isLcpCompleted = true; checkIfSettled(); }); lcpObserver.observe({ type: 'largest-contentful-paint', buffered: true }); // 监听长任务,判断主线程是否空闲 let lastLongTaskEnd = 0; const longTaskObserver = new PerformanceObserver((list) => { const tasks = list.getEntries(); lastLongTaskEnd = tasks[tasks.length - 1].startTime + tasks[tasks.length - 1].duration; }); longTaskObserver.observe({ type: 'longtask', buffered: true }); // 综合判断页面是否稳定 function checkIfSettled() { // 条件:已触发首次输入 + LCP完成 + 主线程500ms内无长任务 const isMainThreadIdle = performance.now() - lastLongTaskEnd > 500; if (isFirstInputReceived && isLcpCompleted && isMainThreadIdle) { // 用requestIdleCallback确保预取不抢占主线程资源 requestIdleCallback(() => { // 通知Service Worker开始预取 navigator.serviceWorker.controller?.postMessage({ action: 'START_PREFETCH' }); }); } else if (isFirstInputReceived && isLcpCompleted) { // 若主线程仍繁忙,延迟500ms再检查 setTimeout(checkIfSettled, 500); } } // 结合页面load事件和可见性判断 document.addEventListener('load', () => { if (document.visibilityState === 'hidden') { // 页面在后台,等可见时再判断 document.addEventListener('visibilitychange', () => { if (document.visibilityState === 'visible') checkIfSettled(); }, { once: true }); } else { checkIfSettled(); } });
2. 辅助判断:利用页面生命周期事件
load事件:页面资源基本加载完毕,但可能仍有渲染任务,需结合其他指标visibilitychange:避免在页面后台时触发预取,浪费资源
二、Service Worker:接收通知并执行预取
Service Worker本身无法直接感知页面的性能状态,必须通过页面发送的消息触发预取操作:
// Service Worker 脚本 self.addEventListener('message', (event) => { if (event.data.action === 'START_PREFETCH') { // 定义需要预取的资源(按需调整) const prefetchTargets = [ '/about', '/assets/css/common.css', '/assets/js/utils.js' ]; // 打开缓存并执行预取 caches.open('prefetch-cache-v1').then(cache => { Promise.all( prefetchTargets.map(target => fetch(target, { mode: 'no-cors' }) .then(res => cache.put(target, res)) .catch(err => console.warn('预取失败:', target, err)) ) ).then(() => { console.log('预取任务完成'); }); }); } });
关键注意事项
- 优先保障核心指标:所有预取操作必须在
requestIdleCallback中执行,避免抢占FP和TTI的主线程资源 - 精准选择预取资源:只预取用户大概率访问的页面/资源,避免浪费带宽和缓存空间
- 兼容处理:部分老浏览器可能不支持
PerformanceObserver,可以降级为监听load事件后延迟几秒触发预取
内容的提问来源于stack exchange,提问作者David Alsh
相关产品推荐
相关产品推荐

