微信小程序导航跟随

408 阅读2分钟

小程序写了好几个,最近搞一个迭代需求:下单。其中有个效果是导航栏跟随的效果,如下是app的效果图:

接到需求后没觉得有什么难度,毕竟vue移动端项目中也做过类似的效果,只不过小程序里没有a标签做锚点,那就动态算呗。跟随也好办,监听滚动条事件,动态算滚动条距离在哪两个元素区间就好了。可就这个需求让我憋了一下午。走到了死胡同。

先总结正确的做法: 首先要定义onload:

async onLoad (options) {
    await this.getData(options.id)
    wx.createSelectorQuery().select('#nav').boundingClientRect(rect=>{
      this.setData({
        //计算nav距离顶部的实际距离
        navDistanceTop:rect.top
      })
    }).exec()
    wx.getSystemInfo({
      success: (result)=>{
        this.setData({
          screenHalfHeight:result.windowHeight/2
        })
      },
      fail: ()=>{},
      complete: ()=>{}
    });
    let offsetArray = []
    let lock = false
    this.data.info.details.forEach((item, index)=>{
      wx.createSelectorQuery().select('.img-cont'+index).boundingClientRect(rect=>{
        // 注意:这个top并不是offsetTop,而是你获取的点距离上次滚动条位置
        offsetArray.push(rect.top)
      }).exec()
      if(index==this.data.info.details.length-1){
        lock = true
      }
    })
    let interval = setInterval(()=>{
      // 获取高度是异步行为,所以要确定已经循环完毕拿到offsettop才setData
      if(lock){
        clearInterval(interval)
        this.setData({
          offsetArray:offsetArray
        })
      }
    },300)
  },

这里有几个点解释一下: getData我需要先拿到这个数组,然后我需要计算出几个值:

  • 吸顶nav到顶部的距离navDistanceTop,用这个距离设置什么时候吸顶
  • 保存图片的模块的offset组成的数组offsetArray
  • 屏幕半高screenHalfHeight 这里之所以要设置定时器,因为for循环虽是同步,但它的回调获取rect.top确是异步的,所以导致整个foreach是异步的,所以我要设置一个锁机制,每隔一段时间泡一下定时器,直到foreach拿到高度数组之后清除,并setData保存下来备用

接下来可以实现类锚点行为了:

// nav选择点击事件
  chooseNavItem(e){
    const currentIndex = e.currentTarget.dataset.index
    wx.pageScrollTo({
      scrollTop:this.data.offsetArray[currentIndex]-this.data.screenHalfHeight,
      duration:500
    })
    setTimeout(() => {
      this.setData({
        currentNavIndex:currentIndex
      })
    }, 500);
  },

锚点这步很简单没什么说的

然后是监听滚动条事件:

  onPageScroll:util.throttle(function(e){
    console.log(e)
    //当前滚动条高度
    const currentDistanceTop = e[0].scrollTop
    this.setData({
      hiddenFixNav:this.data.navDistanceTop>currentDistanceTop,
    })
    const temp = this.data.offsetArray
    const screenHalfHeight = this.data.screenHalfHeight // 可视区域半高
    for(let i in temp){
      if(temp[1]-screenHalfHeight>=currentDistanceTop){
        this.setData({
          currentNavIndex:0
        })
      }else if(i>0&&currentDistanceTop>temp[i-1]-screenHalfHeight&&currentDistanceTop<=temp[i]-screenHalfHeight){
        this.setData({
          currentNavIndex:i-1
        })
      }else if(currentDistanceTop>temp[i]-screenHalfHeight){
        this.setData({
          currentNavIndex:i
        })
      }
    }
    this.setData({
      currentDistanceTop:currentDistanceTop
    })
  },50),

在这里用滚动条高度和元素的offsetTop作比较,还要考虑一个半高 ,总的来说其实发现没太难,但一定要熟悉小程序中每个api以及参数代表的意思,比如rect.pop是上次滚动条距离当前节点的高度,而不是offsetTop,一开始如果在onload中确实是相等的,但如果在每一次滚动后重新算,那么就是不等的,刚开始以为就是offsetTop所以走进了死胡同,所以还是要先查好api,先想好什么时候获取要用的参数,是不是在onload终究算出offsetTopArray。