getCLS.js 3.2 KB

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