LCP 元素判定逻辑

LCP是指从页面开始加载到用户第一次与页面发生交互期间出现过的最大可见image或者文本块元素绘制在屏幕上的时间点。

案例1,不在首屏可视范围内的元素不被计算为LCP

在这里插入图片描述
当元素<h1>Largest Contentful Paint Test</h1>,不在当前屏幕展示时则无法计算LCP
在这里插入图片描述

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>LCP Owned Text Nodes Test</title>
</head>
<body style="padding-top: 100vh;"> <!-- 当pading-top: 100vh时元素不会展示在当前页面 -->
  <h1>Largest Contentful Paint Test</h1>
  <script>
    // 用 PerformanceObserver 监听 LCP 候选项
    const observer = new PerformanceObserver((list) => {
      const entries = list.getEntries();
      for (const entry of entries) {
        console.log('LCP entry:', entry);
        const element = entry.element;
        element.style.outline = '4px solid red';
        element.setAttribute('data-lcp', 'true');
      }
    });

    observer.observe({ type: 'largest-contentful-paint', buffered: true });
  </script>
</body>
</html>

案例2,用户输入后出现的元素不会被计算为LCP

如果在5s内发生过用户与页面的交互行为,那么5s后被添加的元素就不会被算作LCP。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>LCP Owned Text Nodes Test</title>
  <style>
    body {
      font-family: sans-serif;
      margin: 50px;
    }
  </style>
</head>
<body>
  <script>
    setTimeout(() => {
      const h1 = document.createElement('h1')
      h1.innerText = 'Largest Contentful Paint Test'
      document.body.appendChild(h1)
    }, 5000)
    // 用 PerformanceObserver 监听 LCP 候选项
    const observer = new PerformanceObserver((list) => {
      const entries = list.getEntries();
      for (const entry of entries) {
        console.log('LCP entry:', entry);
        const element = entry.element;
        element.style.outline = '4px solid red';
        element.setAttribute('data-lcp', 'true');
      }
    });

    observer.observe({ type: 'largest-contentful-paint', buffered: true });
  </script>
</body>
</html>

案例3,元素出现过被移除也算LCP只要当前元素符合要求

在这里插入图片描述

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>LCP Owned Text Nodes Test</title>
  <style>
    body {
      font-family: sans-serif;
      margin: 50px;
    }
  </style>
</head>
<body>
  <h1>Largest Contentful Paint Test</h1>
  <script>
    setTimeout(() => {
      document.querySelector('h1').remove()
      console.log('h1元素已被移除')
    }, 5000)
    // 用 PerformanceObserver 监听 LCP 候选项
    const observer = new PerformanceObserver((list) => {
      const entries = list.getEntries();
      for (const entry of entries) {
        console.log('LCP entry:', entry);
        const element = entry.element;
        element.style.outline = '4px solid red';
        element.setAttribute('data-lcp', 'true');
      }
    });

    observer.observe({ type: 'largest-contentful-paint', buffered: true });
  </script>
</body>
</html>

LCP(Largest Contentful Paint)用于衡量用户体验,相对于FP,LCP对于用户而言更有意义。但因为LCP是启发式算法也有自身的局限:

  1. LCP算法会在发生用户输入时停止检测,这意味着如果用户操作发生在主要内容绘制之前LCP则没法捕获页面的主要内容,如果用户输入发生的太早甚至LCP算法无法捕获到任何元素。
  2. 出现过的最大图片即使被移除也会被认定为LCP,所以当应用有大的启动图首先展示时LCP算法会将其识别为LCP,但该图片对用户而言并无意义无法标识为对用户有意义的内容加载完成。

元素需要满足如下两个条件才能作为LCP候选元素:

  1. 元素的opacity > 0
  2. 是text node 或图片响应字节数 >= 元素有效视觉尺寸 * 0.004
    如果有效视觉尺寸远大于图片实际大小说明是用一些小的占位图,将其极度放大会很模糊,或者是重复填充,基本是一些无意义内容需要被排除在LCP外

能够成为LCP的元素有两类:

  1. 图片相关元素,<img>、<image> inside an SVG、<video>的海报图、有图片背景的元素
  2. 一组文本节点,例如<span>、<div>

    因为只有图片相关元素和文本节点才会被算入LCP候选,所以只是有背景颜色的元素虽然视觉上可见但是并不会算作LCP

在这里插入图片描述

<div style="background-color: red; height: 100px;"></div>

文本节点:
文本节点需要可见,字体颜色不是透明并且opacity > 0,如果文本节点本身不可见(透明或opacity <= 0)判断text-shadow,stroke-color,stroke-image有一个属性能让文本节点能看到即可纳入LCP候选。
浏览器在渲染文本元素时会按行渲染,每一行都会生成一个对应的border-box,多行文本元素计算渲染面积时浏览器会生成一个最小能够覆盖当前元素下所有文本节点的区域,该区域大小就是LCP的size。如果多行文本之间有其他元素那么该元素占位也会被计算在内。

案例1,正常文本节点size计算

在这里插入图片描述

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>LCP Owned Text Nodes Test</title>
  <style>
    body {
      font-family: sans-serif;
      margin: 50px;
    }
    #lcp-candidate {
      font-size: 24px;
      width: 600px;
      line-height: 1.5;
      border: 1px solid #ccc;
      padding: 20px;
    }
    .spacer {
      height: 40px;
      background-color: rgba(255, 0, 0, 0.1); /* 可视化占位区域 */
    }
  </style>
</head>
<body>
  <h1>Largest Contentful Paint Test</h1>

  <div id="lcp-candidate">
    <div class="spacer"></div>
    This is the first line of a block of text.<br>
    And this is the second line.<br>


    And here is the third line.
  </div>

  <script>
    // 用 PerformanceObserver 监听 LCP 候选项
    const observer = new PerformanceObserver((list) => {
      const entries = list.getEntries();
      for (const entry of entries) {
        console.log('LCP entry:', entry);
        const element = entry.element;
        element.style.outline = '4px solid red';
        element.setAttribute('data-lcp', 'true');
      }
    });

    observer.observe({ type: 'largest-contentful-paint', buffered: true });
  </script>
</body>
</html>

案例2,文本节点之间有其他元素间隔,会撑开包裹所有文本节点的最小矩形

在这里插入图片描述

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>LCP Owned Text Nodes Test</title>
  <style>
    body {
      font-family: sans-serif;
      margin: 50px;
    }
    #lcp-candidate {
      font-size: 24px;
      width: 600px;
      line-height: 1.5;
      border: 1px solid #ccc;
      padding: 20px;
    }
    .spacer {
      height: 40px;
      background-color: rgba(255, 0, 0, 0.1); /* 可视化占位区域 */
    }
  </style>
</head>
<body>
  <h1>Largest Contentful Paint Test</h1>

  <div id="lcp-candidate">
    This is the first line of a block of text.<br>
    And this is the second line.<br>

    <div class="spacer"></div>

    And here is the third line.
  </div>

  <script>
    // 用 PerformanceObserver 监听 LCP 候选项
    const observer = new PerformanceObserver((list) => {
      const entries = list.getEntries();
      for (const entry of entries) {
        console.log('LCP entry:', entry);
        const element = entry.element;
        element.style.outline = '4px solid red';
        element.setAttribute('data-lcp', 'true');
      }
    });

    observer.observe({ type: 'largest-contentful-paint', buffered: true });
  </script>
</body>
</html>

案例3,子元素的文本如果在内联元素中会算作当前元素占据的size

在这里插入图片描述

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>LCP Owned Text Nodes Test</title>
  <style>
    body {
      font-family: sans-serif;
      margin: 50px;
    }
    #lcp-candidate {
      font-size: 24px;
      width: 600px;
      line-height: 1.5;
      border: 1px solid #ccc;
      padding: 20px;
    }
    .spacer {
      height: 40px;
      background-color: rgba(255, 0, 0, 0.1); /* 可视化占位区域 */
    }
  </style>
</head>
<body>
  <h1>Largest Contentful Paint Test</h1>

  <div id="lcp-candidate">
    This is the first line of a block of text.<br>
    <span>
        And this is the second line.
    </span>
    <br>
    <span>spacer spacer spacer spacer spacer spacer spacer</span>
    <span>
      And here is the third line.
    </span>
  </div>

  <script>
    // 用 PerformanceObserver 监听 LCP 候选项
    const observer = new PerformanceObserver((list) => {
      const entries = list.getEntries();
      for (const entry of entries) {
        console.log('LCP entry:', entry);
        const element = entry.element;
        element.style.outline = '4px solid red';
        element.setAttribute('data-lcp', 'true');
      }
    });

    observer.observe({ type: 'largest-contentful-paint', buffered: true });
  </script>
</body>
</html>

案例4,子元素的文本如果在块级元素中则不会算作当前元素占据的size,同案例2,块元素的高度会计算在size内,但宽度不会

在这里插入图片描述

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>LCP Owned Text Nodes Test</title>
  <style>
    body {
      font-family: sans-serif;
      margin: 50px;
    }
    #lcp-candidate {
      font-size: 24px;
      width: 600px;
      line-height: 1.5;
      border: 1px solid #ccc;
      padding: 20px;
    }
    .spacer {
      height: 40px;
      background-color: rgba(255, 0, 0, 0.1); /* 可视化占位区域 */
    }
  </style>
</head>
<body>
  <h1>Largest Contentful Paint Test</h1>

  <div id="lcp-candidate">
    This is the first line of a block of text.<br>
    <span>
And this is the second line.
    </span>
    <br>
    <div>spacer spacer spacer spacer spacer spacer spacer</div>
    <span>
      And here is the third line.
    </span>
  </div>

  <script>
    // 用 PerformanceObserver 监听 LCP 候选项
    const observer = new PerformanceObserver((list) => {
      const entries = list.getEntries();
      for (const entry of entries) {
        console.log('LCP entry:', entry);
        const element = entry.element;
        element.style.outline = '4px solid red';
        element.setAttribute('data-lcp', 'true');
      }
    });

    observer.observe({ type: 'largest-contentful-paint', buffered: true });
  </script>
</body>
</html>

图片节点lcp entry’s size的计算步骤

  1. 元素的imageRequest不是null
  2. 让concreteDimensions等于图片在元素里的实际尺寸
    元素本身尺寸,比如<img width="200" height="100">
    图像缩放(object-fit,background-size等)
  3. 让visibleDimensions等于图像可见部分
    会有object-position或background-position来调整图像位置导致图像只能部分可见
  4. 让clientContentRect等于能够包裹住可见图像的最小尺寸
    会有CSS的transform,scale,translate,rotate之类方式改变图像形状
  5. 让intersectingClientContentRect等于clientContentRect和视口相交的尺寸
    也就是真正在屏幕上能露出来的部分
  6. 让size = intersectingClientContentRect‘s width * height
    计算图像在屏幕上实际可见的面积,是LCP评估的重要指标
  7. 让naturalArea等于图片原始大小
    比如图片原始width = 100px height = 100px,那么naturalArea = 10000
  8. 如果naturalArea是0直接返回空,不计入LCP候选元素
    因为图像实际内容可能未加载,损坏,空图等,直接跳过
  9. 让boundingClientArea等于clientContentRect’width * height,是包裹图像最小矩形面积
  10. 让 scaleFactor = boundingClientArea / naturalArea. 判断图像是否被放大展示
    比如:
    • 原图大小是 100×100(naturalArea = 10,000)
      被放大显示为 200×200(boundingClientArea = 40,000)
    • 则 scaleFactor = 4
      如果 scaleFactor > 1, 将上面计算得到的size除以放大的倍数,计算真正的size。目的是为了防止误认为内容很多,其实图片很小,抹平误差。
      比如:
      <img src="small.jpg" width="100" height="100" style="width: 400px; height: 400px;">
    • naturalArea = 10,000
    • boundingClientArea = 160,000
    • scaleFactor = 16 → 被放大了 16 倍
      → LCP size 会被缩小 16 倍,以避免放大虚报

参考:

  1. Largest Contentful Paint
  2. LargestContentPaint
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值