UNPKG

5.1 kBJavaScriptView Raw
1/*
2 * Copyright 2020 Google LLC
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * https://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16import { onBFCacheRestore } from './lib/bfcache.js';
17import { bindReporter } from './lib/bindReporter.js';
18import { doubleRAF } from './lib/doubleRAF.js';
19import { getActivationStart } from './lib/getActivationStart.js';
20import { getVisibilityWatcher } from './lib/getVisibilityWatcher.js';
21import { initMetric } from './lib/initMetric.js';
22import { observe } from './lib/observe.js';
23import { onHidden } from './lib/onHidden.js';
24import { runOnce } from './lib/runOnce.js';
25import { whenActivated } from './lib/whenActivated.js';
26import { whenIdle } from './lib/whenIdle.js';
27/** Thresholds for LCP. See https://web.dev/articles/lcp#what_is_a_good_lcp_score */
28export const LCPThresholds = [2500, 4000];
29const reportedMetricIDs = {};
30/**
31 * Calculates the [LCP](https://web.dev/articles/lcp) value for the current page and
32 * calls the `callback` function once the value is ready (along with the
33 * relevant `largest-contentful-paint` performance entry used to determine the
34 * value). The reported value is a `DOMHighResTimeStamp`.
35 *
36 * If the `reportAllChanges` configuration option is set to `true`, the
37 * `callback` function will be called any time a new `largest-contentful-paint`
38 * performance entry is dispatched, or once the final value of the metric has
39 * been determined.
40 */
41export const onLCP = (onReport, opts) => {
42 // Set defaults
43 opts = opts || {};
44 whenActivated(() => {
45 const visibilityWatcher = getVisibilityWatcher();
46 let metric = initMetric('LCP');
47 let report;
48 const handleEntries = (entries) => {
49 // If reportAllChanges is set then call this function for each entry,
50 // otherwise only consider the last one.
51 if (!opts.reportAllChanges) {
52 entries = entries.slice(-1);
53 }
54 entries.forEach((entry) => {
55 // Only report if the page wasn't hidden prior to LCP.
56 if (entry.startTime < visibilityWatcher.firstHiddenTime) {
57 // The startTime attribute returns the value of the renderTime if it is
58 // not 0, and the value of the loadTime otherwise. The activationStart
59 // reference is used because LCP should be relative to page activation
60 // rather than navigation start if the page was prerendered. But in cases
61 // where `activationStart` occurs after the LCP, this time should be
62 // clamped at 0.
63 metric.value = Math.max(entry.startTime - getActivationStart(), 0);
64 metric.entries = [entry];
65 report();
66 }
67 });
68 };
69 const po = observe('largest-contentful-paint', handleEntries);
70 if (po) {
71 report = bindReporter(onReport, metric, LCPThresholds, opts.reportAllChanges);
72 const stopListening = runOnce(() => {
73 if (!reportedMetricIDs[metric.id]) {
74 handleEntries(po.takeRecords());
75 po.disconnect();
76 reportedMetricIDs[metric.id] = true;
77 report(true);
78 }
79 });
80 // Stop listening after input. Note: while scrolling is an input that
81 // stops LCP observation, it's unreliable since it can be programmatically
82 // generated. See: https://github.com/GoogleChrome/web-vitals/issues/75
83 ['keydown', 'click'].forEach((type) => {
84 // Wrap in a setTimeout so the callback is run in a separate task
85 // to avoid extending the keyboard/click handler to reduce INP impact
86 // https://github.com/GoogleChrome/web-vitals/issues/383
87 addEventListener(type, () => whenIdle(stopListening), {
88 once: true,
89 capture: true,
90 });
91 });
92 onHidden(stopListening);
93 // Only report after a bfcache restore if the `PerformanceObserver`
94 // successfully registered.
95 onBFCacheRestore((event) => {
96 metric = initMetric('LCP');
97 report = bindReporter(onReport, metric, LCPThresholds, opts.reportAllChanges);
98 doubleRAF(() => {
99 metric.value = performance.now() - event.timeStamp;
100 reportedMetricIDs[metric.id] = true;
101 report(true);
102 });
103 });
104 }
105 });
106};