小程序-实现带字母导航的滚动列表

在前端岗位已经有足足三年了,一直没有写过微信小程序,我知道它很火,有近似原生app的体验却无需安装app,即开即用,非常方便。工作一直挺忙,都是些再熟悉不过的搬砖工作,这大概就叫做瞎忙吧。近来稍闲,就学习了一下微信小程序,做了两个小demo,一个是仿《汽车之家》app里面的带字母导航的列表滚动效果,另外一个是日历控件,这两个都比较常用,放在那里以备后面工作需要,工作中有碰到类似需求的小伙伴也可以拿去用,相互借鉴嘛,碰到bug的话,劳烦给我留个言,我会及时关注解决的。

效果如下:

下面先把列表滚动的组件代码贴出来。

wxml代码如下:

<view class="c-alphabet-nav" style="height: {{ height }}">
<view wx:if="{{ list.length }}">
<scroll-view
bindscroll="listScroll"
scroll-with-animation
scroll-top="{{ scrollTo }}"
scroll-y
class="wrap-scroll">
<view class="list" wx:for="{{ list }}" wx:key="item.alphabet" id="{{ item.alphabet }}">
<view class="list-title">
<text>{{ item.alphabet }}</text>
</view>
<view
class="list-item"
wx:for="{{ item.data }}"
wx:for-item="it"
wx:key="it"
bindtouchstart="tapItem"
data-itemInfo="{{ it }}">
<text>{{ it }}</text>
</view>
</view>
</scroll-view>
<view class="alphaNavWrap" wx:if="{{ alphaNavFlag }}">
<view class="alphaNav" capture-bind:touchmove="touchmoveHandle" bindtouchend="touchendHandle">
<view
wx:for="{{ list }}"
wx:key="item.alphabet"
class="'alphabet' {{ currentAlpha === item.alphabet ? 'active': '' }}"
bindtouchstart="touchstartHandle"
data-alpha="{{item.alphabet}}">
{{ item.alphabet }}
<view wx:if="{{ moveFlag && currentAlpha === item.alphabet }}" class="sign">{{ item.alphabet }}</view>
</view>
</view>
</view>
</view>
<view wx:if="{{ !loading && !list.length }}" class="nodata">
<text>暂无数据</text>
</view>
<view wx:if="{{ loading }}" class="loading">
<text>loading...</text>
</view>
</view>
wxss代码如下:
.c-alphabet-nav {
position: relative;
}
/*滚动区样式*/
.c-alphabet-nav>view {
height: 100%;
}
.c-alphabet-nav .wrap-scroll {
height: 100%;
border-top: 1px solid #ddd;
}
.c-alphabet-nav .list .list-title{
height: 40px;
line-height: 40px;
padding: 0 20rpx;
}
.c-alphabet-nav .list .list-item {
position: relative;
height: 40px;
line-height: 40px;
background-color: #fff;
}
.c-alphabet-nav .list .list-item text {
display: block;
font-size: 14px;
padding: 0 20rpx;
}
.c-alphabet-nav .list .list-item:after {
content: '';
display: block;
100%;
height: 1px;
bottom: 0;
left: 0;
position: absolute;
background-color: #d9d9d9;
}
.c-alphabet-nav .loading, .c-alphabet-nav .nodata {
text-align: center;
padding-top: 100rpx;
font-size: 28rpx;
}
/*字母导航区样式*/
.alphaNavWrap {
20px;
height: 100%;
position: absolute;
top: 0;
right: 14rpx;
display: flex;
align-items: center;
}
.c-alphabet-nav .alphaNav {
font-size: 26rpx;
}
.c-alphabet-nav .alphaNav .alphabet {
40rpx;
height: 40rpx;
line-height: 40rpx;
text-align: center;
padding-right: 6rpx;
box-sizing: border-box;
}
.c-alphabet-nav .alphaNav .alphabet.active {
background-color: rgba(6, 6, 6, 0.3);
border-radius: 50%;
}
.c-alphabet-nav .alphaNav .sign {
position: absolute;
font-size: 40rpx;
left: -200rpx;
top: -30rpx;
100rpx;
height: 100rpx;
text-align: center;
line-height: 100rpx;
border-radius: 50%;
background-color: #ddd;
display: none;
}
.c-alphabet-nav .alphaNav .sign:after {
content: '';
display: block;
0;
height: 0;
border-left: 84rpx solid #ddd;
border-top: 50rpx solid transparent;
border-bottom: 50rpx solid transparent;
position: absolute;
right: -46rpx;
top: 1rpx;
z-index: -1;
}
.c-alphabet-nav .alphaNav .alphabet.active .sign {
display: block;
}

js文件如下
Component({
properties: {
loading: {
type: Boolean,
value: false
},
height: {
type: String,
value: '80vh'
},
list: {
type: Array,
observer (list) {
if (list.length) {
try {
const res = wx.getSystemInfoSync();
this.setData({windowHeight: res.windowHeight})
} catch (e) {
console.error(e)
}
const query = this.createSelectorQuery();
query.select('.wrap-scroll').boundingClientRect((rect) => {
this.data.scrollviewHeight = rect.height
});
if (this.data.alphaNavFlag) {
query.select('.alphaNav').boundingClientRect((rect) => {
this.data.alphaNavPageY = Math.floor(rect.top);
this.data.alphaNavItemHeight = rect.height / this.data.list.length
});
}
query.select('.list-item').boundingClientRect((rect) => {
let tempArray = [];
this.data.list.forEach((item, i) => {
let tempObj = {};
tempObj.alphabet = item.alphabet;
tempObj.height = (item.data.length + 1) * rect.height;
if (i) tempObj.height += tempArray[i - 1].height; // 高度累加
tempArray.push(tempObj);
});
this.setData({'itemHeight': tempArray});

// 当滚动区滑块滑动到底部时,滚动条的位置
let pageHeight = tempArray[tempArray.length - 1].height; // 滚动区内容总高度
let maxScrollTop = pageHeight - this.data.scrollviewHeight;
if (maxScrollTop > 0) {
this.setData({'maxScrollTop': maxScrollTop});
this.setData({ 'alphaNavFlag': true })
} else {
this.setData({ 'alphaNavFlag': false })
}
});
setTimeout(() => {
query.exec();
})
}
}
}
},
data: {
alphaNavFlag: false,
currentAlpha: 'A', // 列表当前滑动到的元素
itemHeight: [], // 列表字母项底部距滚动区文档顶部的高度
scrollTo: 0, // 当前元素项顶部距离滚动区文档顶部的距离
windowHeight: 603, // 设备高度
scrollviewHeight: 0, // 滚动区域高度
alphaNavHeight: 0, // 右侧字母列表高度
down: false,
moveFlag: false,
maxScrollTop: 0 // 页面滑到底时滚动条距顶部的高度
},
methods: {
inputHandle (e) {
this.setData({'searchText': e.detail.value})
},
touchmoveHandle (e) {
this.setData({ 'moveFlag': true });
const distance = e.touches[0].pageY - this.data.alphaNavPageY;
const currentAlpha = this.data.list[Math.floor(distance / this.data.alphaNavItemHeight)].alphabet;
this.setData({ 'currentAlpha': currentAlpha })
this.goScroll();
},
touchendHandle () {
this.setData({ 'moveFlag': false });
},
goScroll () {
let parameter = {
'scrollTo': 0,
'currentAlpha': this.data.currentAlpha
};
let { currentAlpha, list, itemHeight, maxScrollTop } = this.data;
if (currentAlpha !== list[0].alphabet) {
itemHeight.forEach((item, i) => {
if (item.alphabet === currentAlpha) {
parameter.scrollTo = itemHeight[i - 1].height;
this.setData({'down': itemHeight[i - 1].height > maxScrollTop});
}
})
}
this.setData(parameter);
},
tapItem (e) {
this.triggerEvent('tapItem', e.currentTarget.dataset)
},
touchstartHandle (e) {
this.setData({'currentAlpha': e.target.dataset.alpha});
this.goScroll();
},
listScroll (e) {
if (this.data.timeId) {
clearTimeout(this.data.timeId);
}
this.data.timeId = setTimeout(() => {
let currentScrollTop = e.detail.scrollTop; // 滚动条距顶部的实时距离
const { itemHeight, maxScrollTop, down } = this.data;
if (currentScrollTop >= maxScrollTop && down) {
this.setData({'down': false});
return
}
for (let i = 0; i < itemHeight.length; i++) {
if (currentScrollTop < itemHeight[i].height) {
this.setData({
'currentAlpha': itemHeight[i].alphabet
});
break
}
}
}, 100);
},
}
});

原文地址:https://www.cnblogs.com/qddyh/p/11160950.html