字母索引导航-小程序

372 阅读2分钟

功能实现

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);
}