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

前言 大家好,我是林三心,用最通俗易懂的话讲最难的知识点是我的座右铭,基础是进阶的前提是我的初心
背景 当我们在做项目的性能优化的时候,优化首屏时间是一个避不过去的优化方向,但是又有多少人想过这两个东西的区别呢:

  • 白屏时间
  • 首屏时间
并且这两个时间的计算方式又有什么区别呢?接下来我就给大家讲一下吧!
白屏时间 是什么?
白屏时间指的是:页面开始显示内容的时间。也就是:浏览器显示第一个字符或者元素的时间
首屏时间,你说你优化了,那你倒是计算出给给我看啊!
文章图片

怎么算?
我们只需要知道浏览器开始显示内容的时间点,即页面白屏结束时间点即可获取到页面的白屏时间。
因此,我们通常认为浏览器开始渲染 标签或者解析完标签的时刻就是页面白屏结束的时间点。
  • 浏览器支持 performance.timing
    Document

  • 浏览器不支持 performance.timing
    Document

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

为什么不直接用生命周期?
有些小伙伴会说:为啥不直接在App.vue的 mounted 生命周期里计算时间呢?大家可以看看,官网说了 mounted 执行并不代表首屏所有元素加载完毕,所以 mounted 计算出来的时间会偏短。
首屏时间,你说你优化了,那你倒是计算出给给我看啊!
文章图片

为什么不直接用nextTick?
nextTick 回调的时候,首屏的DOM都渲染出来了,但是计算 首屏时间 并不需要渲染所有DOM,所以计算出来的时间会偏长
怎么算?
我们需要利用 MutationObserver 监控DOM的变化,监控每一次DOM变化的分数,计算的规则为:
(1 + 层数 * 0.5),我举个例子:
1 2

以上DOM结构的分数为:
1.5 + 2 + 2.5 + 2.5 = 8.5(分)
首屏时间,你说你优化了,那你倒是计算出给给我看啊!
文章图片

其实在首屏的加载中,会涉及到DOM的增加、修改、删除,所以会触发多次 MutationObserver ,所以会统计出不同阶段的score,我们把这些score存放在一个数组 observerData 中,后面大有用处
首屏时间实践 现在我们开始计算首屏时间吧!
前置准备
  • index.html:html页面
    1 23 4

    • computed.js :计算首屏时间的文件
      const observerData = https://www.it610.com/article/[]let observer = new MutationObserver(() => { // 计算每次DOM修改时,距离页面刚开始加载的时间 const start = window.performance.timing.navigationStart const time = new Date().getTime() - startconst 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 = https://www.it610.com/article/[]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 = https://www.it610.com/article/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 = nullfunction unmountObserver(delay) { if (timer) return timer = setTimeout(() => { // 输出首屏时间 console.log(getFirstScreenTime()) // 终止MutationObserver的监控 observer.disconnect() observer = null clearTimeout(timer) }, delay) }

    计算出首屏时间 1020ms
    【首屏时间,你说你优化了,那你倒是计算出给给我看啊!】首屏时间,你说你优化了,那你倒是计算出给给我看啊!
    文章图片

    总结 我这个计算方法其实很多漏洞,没把删除元素也考虑进去,但是想让大家知道计算首屏时间的计算思想,这才是最重要的,希望大家能理解这个计算思想
    结语 我是林三心,一个热心的前端菜鸟程序员。如果你上进,喜欢前端,想学习前端,那咱们可以交朋友,一起摸鱼哈哈,摸鱼群,加我请备注【思否】
    首屏时间,你说你优化了,那你倒是计算出给给我看啊!
    文章图片

      推荐阅读