当我们进入首页时,可能有很多条目需要显示,但是如果条目太多,我们全部将之显示出来就会造成性能的消耗,比如,我在第一条就找到了需要的或者我就看前面两条我就不想看后面的了,所以,这时候如果使用全部加载的方式无疑是不合适的,比较好的做法就是首先显示一面多的内容,当检测到用户快要(或者已经)下拉到页面底部的时候我们再发出ajax请求来请求更多的内容。

那么,第一步需要做的工作就是如何判断何时用户将页面拉到了底部。

body是滚动的wrap,我们可以获得浏览器的高度、body的scrollTop、以及body的scrollHeight, 如果浏览器的高度 + body的scrollTop接近(达到)body的scrollHeight的值得时候,那么说明快要达到底部了。

如何获取浏览器的高度:

function getViewportSize () {
    return {
        width: window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth,
        height: window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight

这里使用 window.innerWidth 可以准确的获取到浏览器的宽度,通过 window.innerHeight 可以准确的获取到浏览器的高度。  

但是当用户缩放时window.innerWidth和window.innerHeight的值都会发生变化。其他的也是一样的。 

只是我在做微信网页的时候发现,两者的值时不同的,只有 window.innerHeight才能获取到准确的值,

可以看到,这时的body并没有占满一页,所以这时获得的高度必然不是浏览器的高度。 值得注意,至于为什么,后续我会继续研究。

        let windowHeight = window.innerHeight; // 1334px
        let scrollTop = $('body')[0].scrollTop; // 如果在没有滚动的情况下就是0
        let scrollHeight = $('body')[0].scrollHeight; // 即总的高度。如果在不超过一整页的情况下得到的也是 
        console.log(windowHeight + scrollTop);
        console.log(scrollHeight);
        if ((windowHeight + scrollTop) + 50 >= scrollHeight) {
          console.log("start");

如上所示: 我们使用 window.innerHeight 得到浏览器的高度, 使用 scrollTop 得到滚动被卷起来的高度, 使用 scrollHeight 得到页面的总高度。 

如果 windowHeight + scrollTop + 50 大于 scrollHeight 时, 说明到底部了,这里 + 50 是因为这样不会一直到最底下才开始,而是快到的时候就开始,用户体验会好一些。 

接下来就是如何监测高度的变化

  即我们怎么知道到达底部呢? 

  有两种方法  

方法一:使用setInterval定时器来每个一段时间(比如500ms)就循环一次,获取到各种高度,判断是否达到最底部,然后进行相应的懒加载代码, 优点:对浏览器的支持都比较好,所以用起来不会有太大问题。 缺点1:无论我们是否在滑动,定时器在不断的执行,虽然对性能的影响不是很大,但是很明显,这不是他该发挥的地方。缺点二、setInterval只是对某一段函数的重复执行,但是对于我目前的项目而言(即不同种类下的商品都会滚动)显然是需要写多次的。

方法二: 使用onscroll事件, jqury是支持的,但是zepto不支持,所以说我们直接用原生的更好, window.addEventListener("scroll", function () {}, false);即可。 优点1:语义明确,充分发挥了各自的作用。 比较灵敏。 优点2:因为window.addEventListener() 是将scroll事件绑定到了window上,所以这时全局的,在哪里都可以使用,就像RN的write once , run everywhere。   缺点: 据说,其在ios设备上的触发是在 滑动之后, 而不是滑动一开始就触发, 但对于本项目的懒加载功能是没有影响的。 

在移动端,这里使用 ontouchmove 事件处理程序的效果要好一些,因为如果使用scroll,当用户已经拉到了底部的时候,这时候可能就不会触发scroll了,而 touchmove 是一定会触发的。 

对,最终选用第二种方法, 如下所示:

   window.addEventListener('scroll', function(e) {
        let windowHeight = window.innerHeight; // 1334px
        let scrollTop = $('body')[0].scrollTop; // 如果在没有滚动的情况下就是0
        let scrollHeight = $('body')[0].scrollHeight; // 即总的高度。如果在不超过一整页的情况下得到的也是 
        console.log(windowHeight + scrollTop);
        console.log(scrollHeight);
        if ((windowHeight + scrollTop) + 50 >= scrollHeight) {
          console.log("start ajax request");

注意: 这里加 50 还是加一个别的数字,这是一个技巧,要根据情况进行设定, 比如我们希望还没有到底就可以开始加载更多了,就可以多加一些,如果希望到底部才加载更多,就设置加10或者5甚至不加都是可以的

第三步就是需要开始请求更多的数据了

      if ((windowHeight + scrollTop) + 50 >= scrollHeight) {
          console.log("start");
          var contentObj = {
            id: that.$store.state.items[that.$store.state.curIndex].id, 
            index: that.$store.state.curIndex,
            offset: (that.$store.state.offsets[that.$store.state.curIndex] + 10)
          that.getMoreCurContent(contentObj);

 即当满足某一个条件时,我就可以向后台发送请求了,这里的函数 that.getMoreCurContentactions中的,因为异步请求我们一般都放在actions里。

但是这样存在一个问题: 显然在我向下拉的时候, 满足这个条件的情况不只一次,那么就会导致: that.getMoreCurContent被发送了很多次! 

所以为了解决这个问题,我们需要立一个flag,判定是否能进行, 比如,在 state 中我们添加一个 process 数组,数组的长度就是分类的长度,每一个都是一个布尔值,我们一旦发送一个请求,就设置这个布尔值为true,设置了之后,直到请求成功,我们再设置为false,并且如果说,我们之前设定的pageSize为10, 而这次获取的数据为10的情况下才能让 process[某个分类的index]设置为false,如果不为10, 说明没有更多的数据了,那么我们同样在else语句下设置为true即可,这样,就不会再请求了。 

这里有一个好处是 --- 一旦我们将 process 设置为一个数组,那么每个分类的请求之间就相互不会影响了, 而到每一个分类下,我们只需要一个 state.curIndex 来处理当前即可。 因为他们用的是不同的函数。

如下所示:

 created () {
      var that = this;
      window.addEventListener('scroll', function(e) {
        let windowHeight = window.innerHeight; 
        let scrollTop = $('body')[0].scrollTop; 
        let scrollHeight = $('body')[0].scrollHeight;  
        console.log(windowHeight + scrollTop);
        console.log(scrollHeight);
        if ((windowHeight + scrollTop) + 50 >= scrollHeight) {
          console.log("start");
          var contentObj = {
            id: that.$store.state.items[that.$store.state.curIndex].id, 
            index: that.$store.state.curIndex,
            offset: (that.$store.state.offsets[that.$store.state.curIndex] + 10)
          // 表示正在进行中时,不再请求,
          if (that.$store.state.process[that.$store.state.curIndex] == true) {
       // 什么都不做,所以这里不需要这样写,只是这样可能会比较好理解一些。
          } else {
            that.getMoreCurContent(contentObj);

即进入之后,我们就开始监控了, 这里的offsets 也是一个数组,他的好处是可以做到互不影响。 

 getMoreCurContent ({commit, state}, contentObj) {
      // 设置 that.$store.state.process == true
      var boolObj = {
        index: contentObj.index,
        bool: true
      commit(UPDATE_PROCESS, boolObj);
      var items = state.items;
      var content = {
        "isSingle": 1,
        "sbid": 13729792,
        "catalog3": contentObj.id,
        "offset": contentObj.offset,
        "pageSize": 10
      axios.post('/bbg/goods/get_goods_list_wechat', qs.stringify({"data": JSON.stringify(content)}))
      .then(function (response) {
        if (response.data.code == 626) {
          if (response.data.data.length > 0) {
            var a = 0;
            for (let i = 0; i < response.data.data.length; i++) {
              var obj = {
                index: contentObj.index,
                item: response.data.data[i]
              commit(UPDATE_CONTENT, obj);
              a++;
            if (a == 10) {
              alert("哈哈");
              // 如果等于10,说明还有其他的,那么我们就可以把这个分类的offset增加,继续请求数组,如果说刚好没有数据了,那么就是0,后面也会给出相应的处理的。
             var offsetObj = {
               index: contentObj.index,
               offset: contentObj.offset
              commit(UPDATE_OFFSET, offsetObj);
              var boolObj = {
                index: contentObj.index,
                bool: false
              commit(UPDATE_PROCESS, boolObj);
            } else {
              var boolObj = {
                index: contentObj.index,
                bool: true
              commit(UPDATE_PROCESS, boolObj);
      }).catch(function (error) {
        console.log(error);

两个关键点:

  • 第一: 使用offsets数组作为记录,起到请求更多的作用。
  • 第二: 使用process数组作为记录,起到防止发出多次请求的作用。 
  • 遇到的坑:

  • 使用 document.body.clientHeight 和 document.documentElement.clientHeight 得到的高度并不是浏览器的高度(我在vue中确实是这样)。所以使用window.innerHeight 更好一些。 解决方法: 设置 html,body{width: 100%; height: 100%} 可以解决此问题。
  • 使用zepto时,我用$("body").scroll(function () { // doSomeThing }) 时,发现并不奏效,这是因为通过查询api发现zepto并没有支持这个事件, 所以使用zepto时要注意: zepto并没有完全支持jquery的东西。 
  • if ( typeof pageWidth != 'number' ) { if (document.compatMode == 'CSS1Compat') { pageWidth = document.documentElement.clientWidth; pageHeight = document.documentElement.clientHeight; } else { pageWidth = document.body.clientWidth; pageHeight = document.body.clientHeight;