PaintTiming API 可获取首次有效渲染时间(FCP),即浏览器绘制首个文本、图片等内容的时间点,需在 load 或 DOMContentLoaded 后立即通过 performance.getEntriesByType('paint') 获取,单位为毫秒(相对 navigationStart)。

PaintTiming API 是 HTML5 提供的性能监控接口,用于获取页面绘制相关的时间戳,其中 first-contentful-paint(FCP) 就是“首次有效渲染时间”——即浏览器渲染出第一个文本、图片、非空白 canvas 或 SVG 的时间点。它直接反映用户感知的“内容开始出现”的快慢,是核心 Web 指标(Core Web Vitals)之一。
如何通过 PaintTiming 获取 FCP
PaintTiming 数据需在页面加载后通过 performance.getEntriesByType('paint') 获取,且只能在页面加载完成前或刚完成时读取(建议在 load 或 DOMContentLoaded 后立即执行):
- FCP 不是 DOM 加载完成时间,也不是首屏图像加载完的时间,而是浏览器首次将任何“有意义的内容”绘制到屏幕上的时间点(单位:毫秒,相对于
navigationStart); - 必须在页面生命周期早期采集,延迟读取可能错过条目(PaintTiming 条目不会持久化到后续 JS 执行阶段);
- 示例代码:
if ('paint' in performance) {
// 立即尝试获取(推荐在 load 事件中)
window.addEventListener('load', () => {
const paintEntries = performance.getEntriesByType('paint');
const fcpEntry = paintEntries.find(entry => entry.name === 'first-contentful-paint');
if (fcpEntry) {
console.log('FCP:', fcpEntry.startTime); // 如 1248.32
// 可上报至监控系统
reportMetric('FCP', fcpEntry.startTime);
}
});
}
影响 FCP 的关键因素与优化方向
FCP 受阻于资源加载、样式计算、布局和绘制的链式延迟。优化需从前端加载流程切入:
文章转信息图。将文章/笔记转化为手机可读的 HTML 信息图,自动匹配视觉风格。触发场景:文章转图、笔记转图、信息图、转小红书图、做张图、可视化这篇文章、文生图。
- 减少阻塞渲染的资源:移除或 defer 非关键 JS/CSS;内联首屏 CSS(Critical CSS),避免额外 RTT;
-
加快 HTML 解析与构建 DOM:避免长耗时的 script 同步执行;使用
async或defer加载非首屏 JS; -
提升资源加载效率:对关键图片使用
loading="eager",预连接关键域名(<link rel="preconnect">),预加载首屏字体或 hero 图(<link rel="preload">); - 避免强制同步布局(Layout Thrashing):JS 中不要频繁读写 offsetTop/clientWidth 等触发重排的属性;批量读写分离;
结合其他指标交叉验证 FCP 优化效果
单独看 FCP 容易误判,需配合以下指标定位瓶颈:
立即学习“前端免费学习笔记(深入)”;
- LCP(最大内容绘制):若 FCP 快但 LCP 慢,说明首屏有小内容快速渲染,但主区块(如 banner 图、标题)被延迟加载或未优化;
- TTFB(Time to First Byte):若 FCP > TTFB + 200ms,说明客户端处理(解析、样式、布局)耗时高;若 FCP ≈ TTFB,则问题可能在服务端或网络;
- CLS(累积布局偏移):FCP 后若发生大幅重排(如图片无宽高导致回流),虽不影响 FCP 数值,但损害用户体验,需同步修复;
在真实用户监控(RUM)中稳定采集 FCP
为避免采集丢失或时机偏差,生产环境建议:
- 在
document.addEventListener('visibilitychange')中补充检查,防止页面恢复可见时补采; - 对单页应用(SPA),监听路由变化后重新注册 PaintTiming 监听逻辑(部分框架需手动触发);
- 使用
PerformanceObserver替代轮询式getEntriesByType,更及时可靠:
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (entry.name === 'first-contentful-paint') {
console.log('Observed FCP:', entry.startTime);
observer.disconnect(); // 仅需一次
}
}
});
observer.observe({ entryTypes: ['paint'] });


















