spa项目如何计算首屏

时间: 2019-10-08阅读: 741标签: 项目

对于首屏的定义,浏览器没有给出标准的指标,因为不同网站对于首屏的要求也是不尽相同的。我们从谷歌的第一次有效时间(first meaningfull paint)得到了一些启发,例如,一个新闻网站文字跟字体对于它来说是更重要的,而图片是次要的。新闻网站可以认为所有文字或字体加载出来即为首屏。但是对于电商网站来说,电商网站的图片可能更加重要,因为图片占据整个网站的80%以上。所以仅仅字体或文字被加载出来并不能定义为首屏时间。以此可以看出,首屏并不是一个可以通过简单的api就能计算出来的,首屏的方案也是因公司而异的。幸运的是,浏览器提供了各种监测性能及dom的api,可以让我们通过这些api来计算首屏时间。


首屏定义

举个例子,作为一个二手交易的电商平台,转转网站一半以上是由图片组成。我们以优品首页为例。它的页面加载过程看起来是这样的:


<span>由白屏 -> 加载文字、布局 -> 渲染图片 -> 图片完全加载出来</span>

通过上图,我们可以看出来,在图片加载出来之前(第一张图片),我们并不能清楚知道这个页面想要告诉我们什么,上面白色的位置跟下面热卖专区即将是什么内容我们一概不知。直到所有图片加载出来为止,我们才能够清楚的知道整个页面所要表达的意图。

那么我们用一句话来概括电商首屏定义:<span>初次</span>手机<span>屏幕内</span>页面<span>有效元素</span>(图片)<span>完全展现</span>时间。

这里面有几个关键词:初次、屏幕内、有效元素、完全展现。


我们如何判断页面初次有效渲染的时间?

因为现在我们的前端页面大都是spa项目,而spa项目所有的渲染都是通过js来动态渲染的。所以w3c提供的一些api(load、DOMContentLoaded、domComplete)都会计算的不准确。因为我们整个网站html最初只有一个div。

那我们对于spa项目如何做到比较准确的性能统计呢?假如准许你可以使用框架钩子并可以侵入业务代码,那么你可以做哪些事情?

以vue为例,我们混合为每个组件添加一个mounted钩子,并记录mounted时间,最后在onLoad时候,取最后那个mounted时间,即最后一个组件挂载完成的时间为首屏时间。下面伪代码示例。

Vue.mixin({
    mounted() {
        setStore(time)
    }
})

window.addEventListener('load', () => {
    Pref.send(Math.max(...getStore))
}

这种方法在某些场景可以作为首屏时间,因为一个完整的可复用的高可维护的页面它的颗粒度是足够细的。但是你可能有好多疑问?

  • 如果一个页面并没有抽象成组件化,而它所有的渲染都是依赖于接口返回的数据,那么这种计算方式可能是有瑕疵的。
  • spa项目是异步加载的,onload时间是否是初次渲染结束的时间?
  • 还有最重要的一点,它并没有统计到图片下载的时间。
  • 如何判断当前页面dom初次渲染完成时间?
  • 。。。

好吧,我承认这种方式我们很容易找到投机的方式,并做到很好的性能数据。比如我的数据请求是在onload之后,页面使用模版渲染来代替使用组件,组件渲染时机放在onload之后...等等。但是这些操作并不是我们所提倡的,它反而延长了我们页面真正的渲染时间!

那么我们如何另辟蹊径,找到另一个突破口,尽量不侵入代码的情况下而做到准确的首屏时间呢?

在我不知道MutationObserver这个方法之前,我甚至觉得这是不可能做到。

<span>MutationObserver接口提供了监视对DOM树所做更改的能力</span>

<span>我们可以大胆假设,如果通过 MutationObserver 监听页面body,当页面body元素变化最剧烈并达到最大时就是首屏初次渲染完成的时间。让我们试一下吧~</span>

var targetNode = document.body;
var observerOptions = {
  childList: true, // 观察目标子节点的变化,添加或者删除
  subtree: true // 默认为 false,设置为 true 可以观察后代节点
}
var store = [];
var o = n.MutationObserver;
(new o(function () {
  // 计算dom数量并将dom变化时间记录下来,放进store
  store.push({
    num: computedDomNum(), // 计算dom元素,这个后面会讲
    time: performance.now() // 高精度时间获取
  })
  
})).observe(targetNode, observerOptions)

这样确实可以得到dom变化的数量以及速率,但是我们的首页往往是一个很长页面,而下面列表的dom元素被加载出来的时候其实我们并不是很关心,因为有很多已经不在我们的可视范围内了。所以我们需要将页面的元素增加不同的权重。

OK,我们调整一下计算dom的方法,这也是阿里云的计算方法

function r(e, n, t) {
    var i = 0,
        u = e.tagName;
    if ("SCRIPT" !== u && "STYLE" !== u && "META" !== u && "HEAD" !== u) {
      var c = e.children ? e.children.length : 0;
      if (c > 0) for (var a = e.children, l = c - 1; l >= 0; l--) {
        i += r(a[l], n + 1, i > 0);
      }if (i <= 0 && !t) {
        if (!(e.getBoundingClientRect && e.getBoundingClientRect().top < o)) return 0;
      }
      i += 1 + .5 * n;
    }
    return i;
  }

这样我们就可以达到,只计算首屏时间,这段代码的意思就是:只计算页面在屏幕内出现的元素,屏幕之外的元素不会统计在内。每一层子元素的权重会增加0.5,比如一个元素是在第一层那么这个元素的权重就是1.5,如果元素在第五层那么这个元素就是3.5。


接下来解决图片的加载问题

要解决图片加载问题,首先就要找出页面中所有的img跟div的background-image。

如果是img的话,我们可以使用img标签下的src属性获取属性值即可,如果是div的化可以使用 window.getComputedStyle(dom) 方式获取它的属性值

var computedStyle = window.getComputedStyle(dom);
var bgImg = computedStyle.getPropertyValue('background-image') || computedStyle.getPropertyValue('background');

然后通过正则获取图片的链接即可

然后通过 performance.getEntriesByName(element)[0].responseEnd 的方式可以获取到图片的下载时间,与我们计算的dom响应时间相比取最大值。

这个是获取图片的demo

(() => {
  const imgs = []
  const getImageDomSrc = {
    _getImgSrcFromBgImg: function (bgImg) {
      var imgSrc;
      var matches = bgImg.match(/url\(.*?\)/g);
      if (matches && matches.length) {
        var urlStr = matches[matches.length - 1];
        var innerUrl = urlStr.replace(/^url\([\'\"]?/, '').replace(/[\'\"]?\)$/, '');
        if (((/^http/.test(innerUrl) || /^\/\//.test(innerUrl)))) {
          imgSrc = innerUrl;
        }
      }
      return imgSrc;
    },
    // 提取图片链接
    getImgSrcFromDom: function (dom, imgFilter) {
      if (!(dom.getBoundingClientRect && dom.getBoundingClientRect().top < window.innerHeight))
        return false;
      imgFilter = [/(\.)(png|jpg|jpeg|gif|webp|ico|bmp|tiff|svg)/i]
      var src;
      if (dom.nodeName.toUpperCase() == 'IMG') {
        src = dom.getAttribute('src');
      } else {
        var computedStyle = window.getComputedStyle(dom);
        var bgImg = computedStyle.getPropertyValue('background-image') || computedStyle.getPropertyValue('background');
        var tempSrc = this._getImgSrcFromBgImg(bgImg, imgFilter);
        if (tempSrc && this._isImg(tempSrc, imgFilter)) {
          src = tempSrc;
        }
      }
      return src;
    },

    _isImg: function (src, imgFilter) {
      for (var i = 0, len = imgFilter.length; i < len; i++) {
        if (imgFilter[i].test(src)) {
          return true;
        }
      }
      return false;
    },

    f(e) {
      var t = this
        , u = e.tagName;
      if ("SCRIPT" !== u && "STYLE" !== u && "META" !== u && "HEAD" !== u) {
        var b = this.getImgSrcFromDom(e)
        if (b && !imgs.includes(b))
          imgs.push(b)
        var c = e.children ? e.children.length : 0;
        if (c > 0)
          for (var a = e.children, l = c - 1; l >= 0; l--)
            t.f(a[l]);
      }
    }
  }

  getImageDomSrc.f(document.body)
  // 获取到的首屏所有图片
  console.log(imgs) 
  var max = Math.max(...imgs.map(element => {
    if (/^(\/\/)/.test(element))
      element = 'https:' + element;
    try {
      return performance.getEntriesByName(element)[0].responseEnd || 0
    } catch (error) {
      return 0
    }
  }
  ))
  // 所有图片的responseEnd时间跟计算的fmp相比较得出最大值
  console.log(max)
}
)()

这就是性能统计的关键代码,现在许多公司都是使用的这种计算方法,希望通过这篇文章帮助大家了解首屏的计算。各个公司也应该根据自己的业务场景做一些计算上的修改。比如你项目中使用的图片较少,就可以不把图片计算在内,如果你项目对字体比较敏感,那你就应该把字体的加载计算在内...。总之,计算首屏是没有统一标准的,因为所有公司的页面性质是不同的,侧重点也不一样,要根据公司业务的实际情况来计算。


链接: http://www.fly63.com/article/detial/5765

为什么程序员熬夜加班,项目还是会延期?

首先这和你熬夜加班没有半毛线关系,千万别自己感动自己,但凡是互联网项目,出现延期是常有的事情。项目延期之后,一般流程是领导开会,大家讨论,新一任背锅侠,然后下一次还是老样子.

如何在 React 项目中整合 Eslint 和 Prettier?

首先,我们使用官方提供的脚手架 create-react-app 来创建一个项目:Eslint 是一个可以检验代码,并给出报告的工具。它的目标是保证代码的一致性,避免错误。Eslint 为我们提供了 ECMAScript/JavaScript 规范的代码校验

React + es6使用双向锚点,动态生成,也适用单页面路由项目

React页面中,不确定有多少个需要定位的块,根据元素块的个数,生成对应数量的锚点,点击锚点后页面滚动到指定的块。 页面滚动到指定的块,对应的锚点高亮。

项目中的代码都是如何分层的?

说起应用分层,大部分人都会认为这个不是很简单嘛 就controller,service, mapper三层。看起来简单,很多人其实并没有把他们职责划分开,在很多代码中,controller做的逻辑比service还多,service往往当成透传了

vue项目better-scroll使用注意点

created : 中请求数据,ajax是异步的,这个时候可能mounted已经执行完了,也就是dom挂载完了,但数据还没请求回来,无法获取到内部元素(数据渲染出来的dom)高度. 无法渲染内部元素,无法滚动

在Vue项目中使用Eslint+Prettier+Stylelint

首先搭建vue项目,lint选择ESLint + Prettier,配置方式选择In dedicated config files。具体搭建过程这里就不赘述了,如果不熟悉的同学可以点击这里。配置 Stylelint,目前还没有stylelint选项,需要我们自己安装相关的 npm 包

重构项目,你真的准备好了吗?

我相信每个接受过老项目的程序员可能都吐槽过“前人的代码都是屎”。一个已经有些年头的项目,几乎肯定可以看到——到处拷贝来拷贝去的代码,随处可见的拼写错误,头重脚轻的函数……

水印项目的实现以及两种实现方案的选优

通过 attachShadow 这个方法生成一个shadow root 即shadow的根节点,然后在这个根节点下面通过循环语句添加水印,利用position为absolute进行排版,使其铺满容器

vue多页面项目使用全局Loading组件

多页面vue应用中,在请求接口之前统一加上Loading作为未请求到数据之前的过渡展示。由于多页面未使用vuex做状态管理,只在入口统一注册了bus,所以此例子使用eventbus做事件通信。

pm2 快速部署前端项目

pm2 大家应该都知道,主要是用来管理 node 进程,但是同样可以用来部署前端代码。也可以手动添加 public key 到服务器上的 ~/.ssh/authorized_keys,

fly63.com版权所有,内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权,请与小编联系!情况属实本人将予以删除!

广告赞助文章投稿关于web前端网站点搜索站长推荐网站地图站长QQ:522607023

小程序专栏: 土味情话心理测试脑筋急转弯幽默笑话段子句子语录成语大全