Skip to content
大纲

性能分析

主要分析4个方面fcp(衡量白屏时间)、TTI(页面可用性,开始到主要子资源完成渲染,用户最早可交互时间)、LCP(主要内容在可视区域变得可见时间)、CLS(累积布局偏移。量化了在页面加载期间,视口中元素的移动程度)

获取示例

js
export const perfor = () => {
  console.log('start************************************')
  // const observer = new PerformanceObserver(function (list) {
  //   const perfEntries = list.getEntries()
  //   for (const perfEntry of perfEntries) {
  //     console.log(perfEntry.name, perfEntry.startTime)
  //   }
  // })
  // observer.observe({ entryTypes: [ 'paint' ] })
  const longtask: any = []
  let FCP = 0
  let sessionEntries: any = []
  let sessionValue = 0
  const metric: any = {
    value: 0
  }
  const per = new PerformanceObserver((entryList: any) => {
    for (const entry of entryList.getEntries()) {
      /* cls */
      if (!entry.hadRecentInput) {
        const firstSessionEntry = sessionEntries[0]
        const lastSessionEntry = sessionEntries[sessionEntries.length - 1]
        if (sessionValue && entry.startTime - lastSessionEntry.startTime < 1000 && entry.startTime - firstSessionEntry.startTime < 5000) {
          // 如果时间靠近上一个 session, 将本轮 layout-shift 累加进上一个 session
          sessionValue += entry.value
          sessionEntries.push(entry)
        } else {
          // 新起一个 session
          sessionValue = entry.value
          sessionEntries = [ entry ]
        }
        // 如果当前 session 的 value 大于之前的最大值,替换为现在这个大的
        if (sessionValue > metric.value) {
          metric.value = sessionValue
          metric.entries = sessionEntries
          /* 页面稳定性。累积布局偏移。量化了在页面加载期间,视口中元素的移动程度。CLS 推荐值为低于 0.1,越低说明页面跳来跳去的情况就越少,用户体验越好。页面评分占比25% */
          console.log('CLS: ', metric)
        }
      }
      if (entry.entryType === 'largest-contentful-paint') {
        /* 大内容,主要内容在可视区域变得可见时间点,以交互前最近一次报告为准 页面评分占比42% */
        console.log(entry.entryType + ':LCP', entry.startTime, (entry as any).element)
      }
      if (entry.entryType === 'navigation') {
        const Load = entry.loadEventStart - entry.fetchStart
        const DOMContentLoaded = entry.domContentLoadedEventStart - entry.fetchStart
        console.log('DOM加载完成', DOMContentLoaded)
        console.log('页面及资源全部加载完成', Load)
      }
      if (entry.entryType === 'longtask') {
        longtask.push(entry)
      }
      if (entry.entryType === 'first-input') {
        const firstInputDelay = entry.processingStart - entry.startTime
        /* 测量实际能够开始处理事件处理程序所经过的时间,即交互延迟时间  0 - 100 100 - 300 */
        console.log('firstInput-Delay: FID', firstInputDelay)
        console.log('firstInput-Duration', entry.duration)
        console.log('firstInput-Target', entry.target)
        if (!longtask.length) return
        const lastLongTask = longtask.slice(-1)[0]
        /* FCP 之后最长的长任务耗时, 没长任务就是负数 */
        console.log('MPFID', lastLongTask.startTime - entry.startTime)
      }
    }
  })

  window.onload = () => {
    setTimeout(() => {
      const timing = window.performance.timing

      // console.log(timing)
      /* timing1 */
      const { redirectStart, redirectEnd, fetchStart, domainLookupStart, domainLookupEnd, connectStart, connectEnd, secureConnectionStart, requestStart,
        responseStart, responseEnd , domLoading, domComplete, navigationStart, loadEventEnd, domContentLoadedEventStart, domInteractive
      } = timing
      console.log('old start**************************************')
      console.log('重定向 Redirect', redirectEnd - redirectStart)
      console.log('缓存 App cache', domainLookupStart - fetchStart)
      console.log('DNS 解析', domainLookupEnd - domainLookupStart)
      console.log('TCP建连', connectEnd - connectStart)
      console.log('SSL 握手', connectEnd - secureConnectionStart)
      console.log('请求耗时', responseStart - requestStart)
      console.log('响应耗时', responseEnd - responseStart)
      console.log('Processing', domComplete - domLoading)

      console.log('DOMReady', domContentLoadedEventStart - fetchStart)
      console.log('页面完全加载耗时', loadEventEnd - navigationStart)

      console.log('dom 解析耗时', domInteractive - responseEnd)
      console.log('页面请求到收到应答 TTFB', responseStart - navigationStart)
      console.log('剩余资源加载耗时', domComplete - domContentLoadedEventStart)
      console.log('old end**************************************')

      /* 新监听 */
      newPerformance()
      // new PerformanceObserver((list: any) => {
      //   for (const entry of list.getEntries()) {
      //     const Load = entry.loadEventStart - entry.fetchStart
      //     const DOMContentLoaded = entry.domContentLoadedEventStart - entry.fetchStart
      //     console.log('DOM加载完成', DOMContentLoaded)
      //     console.log('页面及资源全部加载完成', Load)
      //   }
      // }).observe({ type: 'navigation', buffered: true })

      /* fp fcp */
      const paint = window.performance.getEntriesByType('paint')
      for (const perfEntry of paint) {
        /* FP 白屏时间,0 - 1000 1000 - 2500 */
        /* FCP 衡量首屏时间 0 - 1800 1800 - 3000 页面评分占比16% */
        console.log(perfEntry.name, perfEntry.startTime, perfEntry)
        if (perfEntry.name === 'first-contentful-paint') {
          FCP = perfEntry.startTime + perfEntry.duration
        }
      }

      /* TTI TBT */
      setTimeout(() => {
        if (!longtask.length) return
        const { startTime, duration } = longtask.slice(-1)[0]
        /* 页面可用性,开始到主要子资源完成渲染,越小,代表用户可以更能早操作页面 0 - 3800 good 3800 - 7300 needs pro 页面评分占比17% */
        console.log('TTI', startTime + duration)
        /* 页面开始渲染到可以操作页面的时间 */
        console.log('TBT', startTime + duration - FCP)
      }, 4000)
    }, 1000)
    /* LCP */
    per.observe({ type: 'largest-contentful-paint', buffered: true })
    /* dom load */
    per.observe({ type: 'navigation', buffered: true })
    per.observe({ type: 'longtask', buffered: true })
    per.observe({ type: 'first-input', buffered: true })
    /* cls */
    per.observe({ type: 'layout-shift',buffered: true })
  }
}

function newPerformance () {
  console.log(performance.getEntriesByType('navigation')[0])
  console.log("new start**************************************")
  const { redirectEnd, redirectStart, domainLookupEnd, domainLookupStart, fetchStart, connectEnd, connectStart,
    secureConnectionStart, responseStart, responseEnd, requestStart, domComplete, domContentLoadedEventStart, loadEventEnd,
    duration, domInteractive, navigationStart
  } = performance.getEntriesByType('navigation')[0] as any
  console.log('重定向 Redirect', redirectEnd - redirectStart)
  console.log('缓存 App cache', domainLookupStart - fetchStart)
  console.log('DNS 解析', domainLookupEnd - domainLookupStart)
  console.log('TCP建连', connectEnd - connectStart)
  console.log('SSL 握手', connectEnd - secureConnectionStart)
  console.log('请求耗时', responseStart - requestStart)
  console.log('响应耗时', responseEnd - responseEnd)
  console.log('Processing', domComplete)

  console.log('DOMReady', domContentLoadedEventStart - fetchStart)
  console.log('页面完全加载耗时', loadEventEnd - responseEnd, duration)

  console.log('dom 解析耗时', domInteractive)
  console.log('页面请求到收到应答 TTFB', responseStart)
  console.log('剩余资源加载耗时', domComplete - domContentLoadedEventStart)
  console.log("new old**************************************")
}

https://jonny-wei.github.io/blog/devops/performance/indicator.html

Released under the MIT License.