getLCP.ts 2.8 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182
  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 {bindReporter} from './lib/bindReporter.js';
  17. import {finalMetrics} from './lib/finalMetrics.js';
  18. import {getFirstHidden} from './lib/getFirstHidden.js';
  19. import {initMetric} from './lib/initMetric.js';
  20. import {observe, PerformanceEntryHandler} from './lib/observe.js';
  21. import {onBFCacheRestore} from './lib/onBFCacheRestore.js';
  22. import {onHidden} from './lib/onHidden.js';
  23. import {ReportHandler} from './types.js';
  24. export const getLCP = (onReport: ReportHandler, reportAllChanges?: boolean) => {
  25. const firstHidden = getFirstHidden();
  26. let metric = initMetric('LCP');
  27. let report: ReturnType<typeof bindReporter>;
  28. const entryHandler = (entry: PerformanceEntry) => {
  29. // The startTime attribute returns the value of the renderTime if it is not 0,
  30. // and the value of the loadTime otherwise.
  31. const value = entry.startTime;
  32. // If the page was hidden prior to paint time of the entry,
  33. // ignore it and mark the metric as final, otherwise add the entry.
  34. if (value < firstHidden.timeStamp) {
  35. metric.value = value;
  36. metric.entries.push(entry);
  37. }
  38. report();
  39. };
  40. const po = observe('largest-contentful-paint', entryHandler);
  41. if (po) {
  42. report = bindReporter(onReport, metric, reportAllChanges);
  43. const stopListening = () => {
  44. if (!finalMetrics.has(metric)) {
  45. po.takeRecords().map(entryHandler as PerformanceEntryHandler);
  46. po.disconnect();
  47. finalMetrics.add(metric);
  48. report();
  49. }
  50. }
  51. // Stop listening after input. Note: while scrolling is an input that
  52. // stop LCP observation, it's unreliable since it can be programmatically
  53. // generated. See: https://github.com/GoogleChrome/web-vitals/issues/75
  54. ['keydown', 'click'].forEach((type) => {
  55. addEventListener(type, stopListening, {once: true, capture: true});
  56. });
  57. onHidden(stopListening, true);
  58. onBFCacheRestore((event) => {
  59. metric = initMetric('LCP');
  60. report = bindReporter(onReport, metric, reportAllChanges);
  61. requestAnimationFrame(() => {
  62. requestAnimationFrame(() => {
  63. metric.value = performance.now() - event.timeStamp;
  64. finalMetrics.add(metric);
  65. report();
  66. });
  67. });
  68. });
  69. }
  70. };