首屏时间,你说你优化了,那你倒是计算出给给我看啊!

位流露台
• 阅读 8998

前言

大家好,我是林三心,用最通俗易懂的话讲最难的知识点是我的座右铭,基础是进阶的前提是我的初心

背景

当我们在做项目的性能优化的时候,优化首屏时间是一个避不过去的优化方向,但是又有多少人想过这两个东西的区别呢:

  • 白屏时间
  • 首屏时间

并且这两个时间的计算方式又有什么区别呢?接下来我就给大家讲一下吧!

白屏时间

是什么?

白屏时间指的是:页面开始显示内容的时间。也就是:浏览器显示第一个字符或者元素的时间
首屏时间,你说你优化了,那你倒是计算出给给我看啊!

怎么算?

我们只需要知道浏览器开始显示内容的时间点,即页面白屏结束时间点即可获取到页面的白屏时间。

因此,我们通常认为浏览器开始渲染 <body> 标签或者解析完 <head> 标签的时刻就是页面白屏结束的时间点。

  • 浏览器支持 performance.timing

    <head>
    <title>Document</title>
    </head>
    <script type="text/javascript">
    // 白屏时间结束点
    var firstPaint = Date.now()
    var start = performance.timing.navigationStart
    console.log(firstPaint - start)
    </script>
  • 浏览器不支持 performance.timing

    <head>
    <title>Document</title>
    <script type="text/javascript">
      window.start = Date.now();
    </script>
    </head>
    <script type="text/javascript">
    // 白屏时间结束点
    var firstPaint = Date.now()
    console.log(firstPaint - window.start)
    </script>

    首屏时间

    是什么?

    首屏时间是指用户打开网站开始,到浏览器首屏内容渲染完成的时间。对于用户体验来说,首屏时间是用户对一个网站的重要体验因素
    首屏时间,你说你优化了,那你倒是计算出给给我看啊!

为什么不直接用生命周期?

有些小伙伴会说:为啥不直接在App.vue的 mounted 生命周期里计算时间呢?大家可以看看,官网说了 mounted 执行并不代表首屏所有元素加载完毕,所以 mounted 计算出来的时间会偏短。

首屏时间,你说你优化了,那你倒是计算出给给我看啊!

为什么不直接用nextTick?

nextTick 回调的时候,首屏的DOM都渲染出来了,但是计算 首屏时间 并不需要渲染所有DOM,所以计算出来的时间会偏长

怎么算?

我们需要利用 MutationObserver 监控DOM的变化,监控每一次DOM变化的分数,计算的规则为:
(1 + 层数 * 0.5),我举个例子:

<body>
    <div>
      <div>1</div>
      <div>2</div>
    </div>
</body>

以上DOM结构的分数为:

1.5 + 2 + 2.5 + 2.5 = 8.5(分)

首屏时间,你说你优化了,那你倒是计算出给给我看啊!

其实在首屏的加载中,会涉及到DOM的增加、修改、删除,所以会触发多次 MutationObserver ,所以会统计出不同阶段的score,我们把这些score存放在一个数组 observerData 中,后面大有用处

首屏时间实践

现在我们开始计算首屏时间吧!

前置准备

  • index.html:html页面

    <!DOCTYPE html>
    <html lang="en">
    <head> </head>
    <body>
      <div>
        <div>
          <div>1</div>
          <div>2</div>
        </div>
        <div>3</div>
        <div>4</div>
      </div>
      <ul id="ulbox"></ul>
    </body>
    <script src="./computed.js"></script>
    <script src="./request.js"></script>
    </html>
  • computed.js :计算首屏时间的文件

    const observerData = []
    
    let observer = new MutationObserver(() => {
    // 计算每次DOM修改时,距离页面刚开始加载的时间
    const start = window.performance.timing.navigationStart
    const time = new Date().getTime() - start
    
    const body = document.querySelector('body')
    const score = computedScore(body, 1)
    // 加到数组 observerData 中
    observerData.push({
      score,
      time
    })
    })
    observer.observe(
    document, {
      childList: true,
      subtree: true
    }
    )
    
    function computedScore(element, layer) {
    let score = 0
    const tagName = element.tagName
    // 排除这些标签的情况
    if (
      tagName !== 'SCRIPT' &&
      tagName !== 'STYLE' &&
      tagName !== 'META' &&
      tagName !== 'HEAD'
    ) {
      const children = element.children
      if (children && children.length) {
        // 递归计算分数
        for (let i = 0; i < children.length; i++) {
          score += computedScore(children[i], layer + 1)
        }
      }
    
      score += 1 + 0.5 * layer
    }
    return score
    }
  • request.js :模拟请求修改DOM

    // 模拟请求列表
    const requestList = () => {
    return new Promise((resolve) => {
      setTimeout(() => {
        resolve(
          [1, 2, 3,
            4, 5, 6,
            7, 8, 9
          ]
        )
      }, 1000)
    })
    }
    
    const ulbox = document.getElementById('ulbox')
    
    // 模拟请求数据渲染列表
    const renderList = async () => {
    const list = await requestList()
    const fragment = document.createDocumentFragment()
    for (let i = 0; i < list.length; i++) {
      const li = document.createElement('li')
      li.innerText = list[i]
      fragment.appendChild(li)
    }
    ulbox.appendChild(fragment)
    }
    
    // 模拟对列表进行轻微修改
    const addList = async () => {
    const li = document.createElement('li')
    li.innerText = '加上去'
    ulbox.appendChild(li)
    }
    
    (async () => {
    // 模拟请求数据渲染列表
    await renderList()
    // 模拟对列表进行轻微修改
    addList()
    })()

observerData

当我们一切准备就绪后运行代码,我们获得了 observerData ,我们看看它长什么样?

首屏时间,你说你优化了,那你倒是计算出给给我看啊!

计算首屏时间

我们怎么根据 observerData 来计算首屏时间呢?我们可以这么算:下次分数比上次分数增加幅度最大的时间作为首屏时间

很多人会问了,为什么不是取最后一项的时间来当做首屏时间呢?大家要注意了:首屏并不是所有DOM都渲染,我就拿刚刚的代码来举例吧,我们渲染完了列表,然后再去增加一个li,那你是觉得哪个时间段算是首屏呢?应该是渲染完列表后算首屏完成,因为后面只增加了一个li,分数的涨幅较小,可以忽略不计

首屏时间,你说你优化了,那你倒是计算出给给我看啊!

所以我们开始计算吧:

const observerData = []

let observer = new MutationObserver(() => {
  // 计算每次DOM修改时,距离页面刚开始加载的时间
  const start = window.performance.timing.navigationStart
  const time = new Date().getTime() - start
  const body = document.querySelector('body')
  const score = computedScore(body, 1)
  observerData.push({
    score,
    time
  })

  // complete时去调用 unmountObserver
  if (document.readyState === 'complete') {
    // 只计算10秒内渲染时间
    unmountObserver(10000)
  }
})
observer.observe(
  document, {
    childList: true,
    subtree: true
  }
)

function computedScore(element, layer) {
  let score = 0
  const tagName = element.tagName
  // 排除这些标签的情况
  if (
    tagName !== 'SCRIPT' &&
    tagName !== 'STYLE' &&
    tagName !== 'META' &&
    tagName !== 'HEAD'
  ) {
    const children = element.children
    if (children && children.length) {
      // 递归计算分数
      for (let i = 0; i < children.length; i++) {
        score += computedScore(children[i], layer + 1)
      }
    }

    score += 1 + 0.5 * layer
  }
  return score
}

// 计算首屏时间
function getFirstScreenTime() {
  let data = null
  for (let i = 1; i < observerData.length; i++) {
    // 计算幅度
    const differ = observerData[i].score - observerData[i - 1].score
    // 取最大幅度,记录对应时间
    if (!data || data.rate <= differ) {
      data = {
        time: observerData[i].time,
        rate: differ
      }
    }
  }
  return data
}

let timer = null

function unmountObserver(delay) {
  if (timer) return
  timer = setTimeout(() => {
    // 输出首屏时间
    console.log(getFirstScreenTime())
    // 终止MutationObserver的监控
    observer.disconnect()
    observer = null
    clearTimeout(timer)
  }, delay)
}

计算出首屏时间 1020ms

首屏时间,你说你优化了,那你倒是计算出给给我看啊!

总结

我这个计算方法其实很多漏洞,没把删除元素也考虑进去,但是想让大家知道计算首屏时间的计算思想,这才是最重要的,希望大家能理解这个计算思想

结语

我是林三心,一个热心的前端菜鸟程序员。如果你上进,喜欢前端,想学习前端,那咱们可以交朋友,一起摸鱼哈哈,摸鱼群,加我请备注【思否】

首屏时间,你说你优化了,那你倒是计算出给给我看啊!

点赞
收藏
评论区
推荐文章
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(
Wesley13 Wesley13
4年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
凯特林 凯特林
4年前
Vue 项目性能优化—实践指南
Vue项目性能优化—实践指南前言Vue框架通过数据双向绑定和虚拟DOM技术,帮我们处理了前端开发中最脏最累的DOM操作部分,我们不再需要去考虑如何操作DOM以及如何最高效地操作DOM;但Vue项目中仍然存在项目首屏优化、Webpack编译配置优化等问题,所以我们仍然需要去关注Vue项目性能方面的优化,使项目具有更高效
Wesley13 Wesley13
4年前
Java日期时间API系列31
  时间戳是指格林威治时间1970年01月01日00时00分00秒起至现在的总毫秒数,是所有时间的基础,其他时间可以通过时间戳转换得到。Java中本来已经有相关获取时间戳的方法,Java8后增加新的类Instant等专用于处理时间戳问题。 1获取时间戳的方法和性能对比1.1获取时间戳方法Java8以前
Souleigh ✨ Souleigh ✨
4年前
Vue 性能优化
前言Vue框架通过数据双向绑定和虚拟DOM技术,帮我们处理了前端开发中最脏最累的DOM操作部分,我们不再需要去考虑如何操作DOM以及如何最高效地操作DOM;但Vue项目中仍然存在项目首屏优化、Webpack编译配置优化等问题,所以我们仍然需要去关注Vue项目性能方面的优化,使项目具有更高效的性能、更好的用户体验。本文是作者通过实际
御弟哥哥 御弟哥哥
5年前
MySQL索引原理老夫一把梭,从头到尾全讲透了
索引,可能让好很多人望而生畏,毕竟每次面试时候MySQL的索引一定是必问内容,哪怕先撇开面试,就在平常的开发中,对于SQL的优化也而是重中之重。可以毫不夸张的说,系统中SQL的好坏,是能直接决定你系统的快慢的。但是在优化之前大家是否想过一个问题?那就是:我们优化的原则是什么?优化SQL的理论基础是什么?虽然说实践出真知,但是我更相信理论是
Chase620 Chase620
4年前
面试官问 Vue 性能优化,我该怎么回答
前言Vue框架通过数据双向绑定和虚拟DOM技术,帮我们处理了前端开发中最脏最累的DOM操作部分,我们不再需要去考虑如何操作DOM以及如何最高效地操作DOM;但Vue项目中仍然存在项目首屏优化、Webpack编译配置优化等问题,所以我们仍然需要去关注Vue项目性能方面的优化,使项目具有更高效的性能、更好的用户体验。本文是作者通过
Bill78 Bill78
5年前
python装饰器详解
你会Python嘛?我会!那你给我讲下Python装饰器吧!Python装饰器啊?我没用过哎以上是我一个哥们面试时候发生的真实对白。\分割线
Karen110 Karen110
4年前
​一篇文章总结一下Python库中关于时间的常见操作
前言本次来总结一下关于Python时间的相关操作,有一个有趣的问题。如果你的业务用不到时间相关的操作,你的业务基本上会一直用不到。但是如果你的业务一旦用到了时间操作,你就会发现,淦,到处都是时间操作。。。所以思来想去,还是总结一下吧,本次会采用类型注解方式。time包importtime时间戳从1970年1月1日00:00:00标准时区诞生到现在
Easter79 Easter79
4年前
Twitter的分布式自增ID算法snowflake (Java版)
概述分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移
23年通天塔搭建页前端性能优化阶段分享
前言通天塔搭建页项目是用来搭建各类活动页面,比较老且业务复杂的项目,可优化点还是非常多的。今年侧重对运营页首屏加载的性能优化,在保证系统稳定可控、需求持续迭代前提下,最终提升了58.8%速度。在此非常感谢通天塔产品组、后端组、前端组同学,对项目性能优化大力