功能实现
1、索引跟随列表变化
1.1 实现思路
想要实现索引跟随列表变化的效果,我们需要知道以下两个top:
- 页面渲染完成时,各个字母title上边界坐标,也就是top值;
- 页面在垂直方向已滚动的距离,也就是scrollTop;
如果scrollTop值在top1和top2之间,那么我们就可以使top1对应的字母索引处于选中状态
1.2 代码实现
为保证在页面渲染完成之后再获取字母标题的位置,我们需要在onReady钩子函数里完成这个操作,另外我们需要借助小程序提供的API:createSelectorQuery去获取字母title的位置:
onReady() {
// 页面渲染完成时,获取各个字母标题的位置,以便滚动时,字母索引定位
this.$mpUpdated(() => {
// 小程序视图更新完成
// this.brands 字母列表
if (this.brands.length !== 0) {
// 遍历字母数组,记录每个字母的位置信息
this.brands.forEach((item, i) => {
if (i !== 0) {
let letter = `.${item.letterName}`;
let query = this.$api.createSelectorQuery().in(this);
query.select(letter).boundingClientRect().exec(res => {
this.lettersArr.push({
name: item.letterName,
top: Math.floor(res[0].top)
});
});
}
});
}
});
}
当页面发生滚动的时候,我们需要实时获取页面当前滚动的距离,便可知道页面现在处于什么位置,这个我们可以在onPageScroll钩子函数里面实现;获取位置之后我们再与之前的字母位置信息进行比较,也就知道页面当前处于哪个字母区间位置:
onPageScroll(e) {
...
this.lettersArr.forEach((item, i) => {
let currentTop = item.top;
let maxIndex = this.lettersArr.length - 1;
let maxTop = i === maxIndex ? currentTop : this.lettersArr[i + 1].top;
if (e.scrollTop > currentTop && e.scrollTop < maxTop) {
// this.isSelect 可控字母索引的样式
this.isSelect = item.name;
}
});
}
当然为了更完美的效果我们还需要完善一下,也就是当页面触底时,最后一个字母索引为选中样式,这一步可以在onReachBottom钩子函数里做(如果不这样设置,可能会出现最后的字母索引永远不会被选中)
onReachBottom() {
// 监听触底事件,将字母索引z选中
this.isSelect = 'Z';
}
到此,我们的第一个需求“索引跟随列表变化”算式完成了,那么我们再看看如何实现“点击/拖动右侧索引,列表自动滚动到指定位置”
2、点击/拖动右侧索引,列表自动滚动到指定位置
2.1 字母点击索引
简而言之就是计算某个字母标题相对于文档的位置,,再借助小程序API:pageScrollTo实现滚动就行了,所以先上一张图:
如图,当我们确定了要滚动到的位置,也就是t标题的位置,我们只需要拿到文档卷去的高度(scrollTop)和该标题相对于屏幕的坐标(top,可为负值)即可;所以我们先要做两件事:
- createSelectorQuery 拿到目标位置的top值
- 拿到当前的scrollTop,这一步其实我们在onPageScroll钩子函数实现,来补充一下代码
onPageScroll(e) {
// 相当于scrollTop,以便计算字母标题相对于文档的高度
this.baseTop = e.scrollTop;
...
}
拿到scrollTop之后,接下来我们就可以在点击事件中拿到目标字母标题,并获取位置,从而确认目标位置,实现字母点击索引:
// 字母索引定位-点击
// 绑定在每个字母索引上的点击事件
handleLetterClick(e) {
let letter = e.currentTarget.dataset.letter;
// 将右边的字母索引设置为选中样式
this.isSelect = letter;
// 每个字母标题的类名以该字母名命名,例如A标题的类名:A,可通过类名选中该标题节点
let query = `.${letter}`;
this.selectAndScrollTo(query);
}
// 获取位置并滚动
selectAndScrollTo(name) {
let that = this;
// 调用createSelectorQuery API 获取位置
let query = that.$api.createSelectorQuery().in(that);
query.select(name).boundingClientRect().exec(res => {
// 在onPageScroll中设置的baseTop,相当于scrollTop
let targetTop = that.baseTop + res[0].top - that.titleHeight;
// 调用 pageScrollTo
that.$api.pageScrollTo({
scrollTop: targetTop,
duration: 0,
success(res) {
console.log('pageScrollTo success', res);
},
fail(err) {
console.log('pageScrollTo fail', err);
}
});
});
}
2.2 字母拖动索引
同点击索引一样,我们仍需知道相应的字母标题相对于文档的位置,实现滚动,但在次之前我们需要确认我们的目标字母标题是哪个?
要想知道手指滑动到哪个字母索引,只要知道手指距离索引列表顶部的距离,用这个距离长度处以每个字母索引的高度,就可以准确定位字母了。所以怎么知道这个距离长度呢,来看一张图:
如图:计算距离长度,我们可以用当前触点距离屏幕顶部的距离(clientY)减去索引列表距离屏幕顶部的位置,因此我们需要监听touchmove事件(*在索引列表上绑定touchmove事件):
// 绑定在整个索引列表上的touchmove事件
handleLetterMove(e) {
// 获取触点到屏幕顶部的距离
let y = e.changedTouches[0].clientY;
// 获取索引列表顶部距离屏幕的位置(列表fixed定位)
let top = e.currentTarget.offsetTop;
// 计算触点到列表顶部的距离
y = y - top;
// 找到触点所在字母的index
let letterIndex = Math.floor(y / this.letterItemHeight);
// 根据index找到对应的字母,确认目标
let letterName = this.brands[letterIndex].letterName;
// 设置选中样式
this.isSelect = letterName;
let query = `.${letterName}`;
// 调用selectAndScrollTo()
this.selectAndScrollTo(query);
}