
顶部的头部区域不跟随列表滚动; 头部区域以下属于滚动区域。
原理介绍
这个地方的实现主要借助了微信小程序原生的scroll-view组件。
使用它的 scroll-into-view 属性,可以实现点击顶部的tab栏,将页面滚动到指定的列表位置;
使用 bindscroll 事件,可以知道当前页面滚动的距离,根据滚动的距离做tab栏的切换操作;
页面布局代码
先说下界面的整体布局,主要分为两部分,头部固定区域 + 可滚动列表区域。
可滚动的列表区域的标题栏当滚动一定的距离后,它也要固定在顶部。
index.wxml
<view class="list">
<!--顶部固定区域-->
<view style="height:88rpx;width:100%;background-color: burlywood;text-align:center;">头部区域</view>
<!--可滚动区域-->
<scroll-view scroll-y="true" style="width:100%;height:{{scrollAreaHeight}}px;" bindscroll="scroll" scroll-into-view="{{scrollToItem}}" scroll-with-animation="true" scroll-top="{{scrollTop}}">
<!--水平滚动的tab栏-->
<scroll-view scroll-x="true" style="height: 88rpx;width: 100%;">
<view class="head-area {{float?'head-float':''}}">
<view class="head-area-item {{curSelectTab === index?'head-area-item-select':''}}" wx:for="{{appGroupList}}" bindtap="tabClick" data-index="{{index}}">
{{item.name}}
</view>
</view>
</scroll-view>
<!--数据列表-->
<view class="list-group" style="height: {{listGroupHeight}}px;">
<view class="list-group-item" id="v_{{index}}" wx:for="{{appGroupList}}" data-index="{{index}}">
<view class="group-name">
{{item.name}}
</view>
<view class="group-children">
<view wx:for="{{item.children}}" class="group-children-item" style="width: {{itemWidth}}px;">
<image src="{{item.url}}"></image>
<view>{{item.name}}</view>
</view>
</view>
</view>
</view>
</scroll-view>
</view>
index.js
Page({
heightArr: [],
//记录scroll-view滚动过程中距离顶部的高度
distance: 0,
data: {
appGroupList: [
{
name: "分组01",
children: [{
"name": "测试0",
"url": "/images/bluetooth.png"
},
{
"name": "测试1",
"url": "/images/bluetooth.png"
},
{
"name": "测试2",
"url": "/images/bluetooth.png"
},
{
"name": "测试3",
"url": "/images/bluetooth.png"
},
{
"name": "测试4",
"url": "/images/bluetooth.png"
},
{
"name": "测试5",
"url": "/images/bluetooth.png"
},
{
"name": "测试6",
"url": "/images/bluetooth.png"
},
{
"name": "测试7",
"url": "/images/bluetooth.png"
}
]
},
{
name: "分组02",
children: [{
"name": "测试0",
"url": "/images/bluetooth.png"
},
{
"name": "测试1",
"url": "/images/bluetooth.png"
},
{
"name": "测试2",
"url": "/images/bluetooth.png"
},
{
"name": "测试3",
"url": "/images/bluetooth.png"
},
{
"name": "测试4",
"url": "/images/bluetooth.png"
},
{
"name": "测试5",
"url": "/images/bluetooth.png"
},
{
"name": "测试6",
"url": "/images/bluetooth.png"
},
{
"name": "测试7",
"url": "/images/bluetooth.png"
}
]
},
{
name: "分组03",
children: [{
"name": "测试0",
"url": "/images/bluetooth.png"
},
{
"name": "测试1",
"url": "/images/bluetooth.png"
},
{
"name": "测试2",
"url": "/images/bluetooth.png"
},
{
"name": "测试3",
"url": "/images/bluetooth.png"
},
{
"name": "测试4",
"url": "/images/bluetooth.png"
},
{
"name": "测试5",
"url": "/images/bluetooth.png"
},
{
"name": "测试6",
"url": "/images/bluetooth.png"
},
{
"name": "测试7",
"url": "/images/bluetooth.png"
}
]
},
{
name: "分组04",
children: [{
"name": "测试0",
"url": "/images/bluetooth.png"
},
{
"name": "测试1",
"url": "/images/bluetooth.png"
},
{
"name": "测试2",
"url": "/images/bluetooth.png"
},
{
"name": "测试3",
"url": "/images/bluetooth.png"
},
{
"name": "测试4",
"url": "/images/bluetooth.png"
},
{
"name": "测试5",
"url": "/images/bluetooth.png"
},
{
"name": "测试6",
"url": "/images/bluetooth.png"
},
{
"name": "测试7",
"url": "/images/bluetooth.png"
}
]
},
{
name: "分组05",
children: [{
"name": "测试0",
"url": "/images/bluetooth.png"
},
{
"name": "测试1",
"url": "/images/bluetooth.png"
},
{
"name": "测试2",
"url": "/images/bluetooth.png"
},
{
"name": "测试3",
"url": "/images/bluetooth.png"
},
{
"name": "测试4",
"url": "/images/bluetooth.png"
},
{
"name": "测试5",
"url": "/images/bluetooth.png"
},
{
"name": "测试6",
"url": "/images/bluetooth.png"
},
{
"name": "测试7",
"url": "/images/bluetooth.png"
}
]
},
],
itemWidth: wx.getSystemInfoSync().windowWidth / 4,
scrollAreaHeight: wx.getSystemInfoSync().windowHeight - 44,
float: false,
curSelectTab: 0,
scrollToItem: null,
scrollTop: 0, //到顶部的距离
listGroupHeight: 0,
},
onReady: function () {
this.cacluItemHeight();
},
scroll: function (e) {
console.log("scroll:", e);
if (e.detail.scrollTop >= 44) {
this.setData({
float: true
})
} else if (e.detail.scrollTop < 44) {
this.setData({
float: false
})
}
let scrollTop = e.detail.scrollTop;
let current = this.data.curSelectTab;
if (scrollTop >= this.distance) {
//页面向上滑动
//列表当前可视区域最底部到顶部的距离 超过 当前列表选中项距顶部的高度(且没有下标越界),则更新tab栏
if (current + 1 < this.heightArr.length && scrollTop >= this.heightArr[current]) {
this.setData({
curSelectTab: current + 1
})
}
} else {
//页面向下滑动
//如果列表当前可视区域最顶部到顶部的距离 小于 当前列表选中的项距顶部的高度,则切换tab栏的选中项
if (current - 1 >= 0 && scrollTop < this.heightArr[current - 1]) {
this.setData({
curSelectTab: current - 1
})
}
}
//更新到顶部的距离
this.distance = scrollTop;
},
tabClick(e) {
this.setData({
curSelectTab: e.currentTarget.dataset.index,
scrollToItem: "v_" + e.currentTarget.dataset.index
})
},
//计算每一个item高度
cacluItemHeight() {
let that = this;
this.heightArr = [];
let h = 0;
const query = wx.createSelectorQuery();
query.selectAll('.list-group-item').boundingClientRect()
query.exec(function (res) {
res[0].forEach((item) => {
h += item.height;
that.heightArr.push(h);
})
console.log(that.heightArr);
that.setData({
listGroupHeight: that.heightArr[that.heightArr.length - 1]
})
})
},
})
index.wxss
.list {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
}
.head-area {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
height: 88rpx;
width: 100%;
padding: 0 10;
}
.head-area-item {
display: flex;
height: 88rpx;
text-align: center;
width: 150rpx;
align-items: center;
justify-content: center;
}
.head-area-item-select {
color: #09bb07;
}
image {
width: 88rpx;
height: 88rpx;
}
.list-group {
display: flex;
width: 100%;
height: 1000%;
flex-direction: column;
}
.list-group-item {
display: flex;
width: 100%;
background-color: #aaa;
flex-direction: column;
}
.group-name {
height: 88rpx;
display: flex;
text-align: center;
align-items: center;
margin-left: 20rpx;
}
.group-children {
display: flex;
flex-direction: row;
flex-wrap: wrap;
width: 100%;
}
.group-children-item {
height: 160rpx;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.head-float {
position: fixed;
top: 88rpx;
background-color: #ffffff;
}
在逻辑代码中最主要的有两个地方:
1、cacluItemHeight 计算列表中item的高度数组,并将最终计算的结果保存在 heightArr数组中。
heightArr数组中的每一项的值是在前一项的基础之上进行累加。
2、scroll 中判断当前的滚动方向,根据滚动判断当前的方向,然后根据滚动的距离设置当前选择的tab。
好了,就这么多,基于以上的内容基本可以实现想要的滚动联动、切换tab联动效果。
来源:https://www.finclip.com/news/f/85230.html