小程序写了好几个,最近搞一个迭代需求:下单。其中有个效果是导航栏跟随的效果,如下是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&¤tDistanceTop>temp[i-1]-screenHalfHeight&¤tDistanceTop<=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。