UNPKG

3.35 kBPlain TextView 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 */
16
17import {initMetric} from './lib/initMetric.js';
18import {observe, PerformanceEntryHandler} from './lib/observe.js';
19import {onHidden} from './lib/onHidden.js';
20import {onBFCacheRestore} from './lib/onBFCacheRestore.js';
21import {bindReporter} from './lib/bindReporter.js';
22import {getFCP} from './getFCP.js';
23import {ReportHandler} from './types.js';
24
25
26// https://wicg.github.io/layout-instability/#sec-layout-shift
27interface LayoutShift extends PerformanceEntry {
28 value: number;
29 hadRecentInput: boolean;
30}
31
32
33let isMonitoringFCP = false;
34let fcpValue = -1;
35
36export const getCLS = (onReport: ReportHandler, reportAllChanges?: boolean) => {
37 // Start monitoring FCP so we can only report CLS if FCP is also reported.
38 // Note: this is done to match the current behavior of CrUX.
39 if (!isMonitoringFCP) {
40 getFCP((metric) => {
41 fcpValue = metric.value;
42 });
43 isMonitoringFCP = true;
44 }
45
46 const onReportWrapped: ReportHandler = (arg) => {
47 if (fcpValue > -1) {
48 onReport(arg);
49 }
50 };
51
52 let metric = initMetric('CLS', 0);
53 let report: ReturnType<typeof bindReporter>;
54
55 let sessionValue = 0;
56 let sessionEntries: PerformanceEntry[] = [];
57
58 const entryHandler = (entry: LayoutShift) => {
59 // Only count layout shifts without recent user input.
60 if (!entry.hadRecentInput) {
61 const firstSessionEntry = sessionEntries[0];
62 const lastSessionEntry = sessionEntries[sessionEntries.length - 1];
63
64 // If the entry occurred less than 1 second after the previous entry and
65 // less than 5 seconds after the first entry in the session, include the
66 // entry in the current session. Otherwise, start a new session.
67 if (sessionValue &&
68 entry.startTime - lastSessionEntry.startTime < 1000 &&
69 entry.startTime - firstSessionEntry.startTime < 5000) {
70 sessionValue += entry.value;
71 sessionEntries.push(entry);
72 } else {
73 sessionValue = entry.value;
74 sessionEntries = [entry];
75 }
76
77 // If the current session value is larger than the current CLS value,
78 // update CLS and the entries contributing to it.
79 if (sessionValue > metric.value) {
80 metric.value = sessionValue;
81 metric.entries = sessionEntries;
82 report();
83 }
84 }
85 };
86
87 const po = observe('layout-shift', entryHandler as PerformanceEntryHandler);
88 if (po) {
89 report = bindReporter(onReportWrapped, metric, reportAllChanges);
90
91 onHidden(() => {
92 po.takeRecords().map(entryHandler as PerformanceEntryHandler);
93 report();
94 });
95
96 onBFCacheRestore(() => {
97 sessionValue = 0;
98 fcpValue = -1;
99 metric = initMetric('CLS', 0);
100 report = bindReporter(onReportWrapped, metric, reportAllChanges);
101 });
102 }
103};