v146 · Performance · Largest Contentful Paint

LCP match specced behavior for emitting candidates

Chrome 146 aligns the Largest Contentful Paint API's candidate-emitting logic with the specification. Previously Chrome emitted LCP candidates in situations the spec says should be skipped — for example, elements with zero intrinsic size or elements not visible in the viewport. The fix makes LCP scores more consistent with other browser implementations.

concepts

  1. LCP Demo

    Uses PerformanceObserver to watch largest-contentful-paint entries and shows which elements Chrome 146 selects as candidates vs. which the old code would have incorrectly included.

  2. Candidate Rules

    The specification rules for what qualifies as an LCP candidate — element types, visibility requirements, size computation, and the conditions that disqualify an element in Chrome 146.

  3. Candidate timeline trace

    Replays a six-candidate page load on a real timeline, lets you flip between the old "≥ previous" rule and the new spec "> previous" rule, and shows how many entries each emits.

  4. LCP Audit Tool

    A live PerformanceObserver watches largest-contentful-paint entries with feature detection. Inject large images, text blocks, and headings into the stage area — LCP winners get a green outline. A candidate table shows element type, size, render time, load time, and spec-eligibility badges. Four pitfall testers cover lazy images, CSS backgrounds, empty elements, and removed elements.

why it shipped

LCP (Largest Contentful Paint) measures when the largest content element in the viewport finishes rendering. The Largest Contentful Paint specification defines precisely which elements can be candidates and when candidates should be emitted. Chrome had several deviations from the spec — elements that were off-screen, had zero size, or were not visible in the traditional sense were sometimes included as LCP candidates. Chrome 146 fixes these deviations, making LCP metrics more accurate and consistent with Firefox and Safari's implementations.

the change

// Observing LCP candidates — unchanged API
const observer = new PerformanceObserver(list => {
  for (const entry of list.getEntries()) {
    console.log('LCP candidate:', {
      element: entry.element?.tagName,
      size: entry.size,                  // intrinsic size × intersection ratio
      startTime: entry.startTime,        // render time
      url: entry.url,                    // for images
    });
  }
});
observer.observe({ type: 'largest-contentful-paint', buffered: true });

// Chrome 146 no longer emits candidates for:
// - Elements with entry.size === 0 (no intrinsic size)
// - Images with 0×0 dimensions
// - Elements entirely outside the viewport at paint time
// - Elements with opacity: 0 or visibility: hidden

references