小程序 scroll-view 实现滚动高亮左侧 tab 导航栏锚点定位

效果图:
在这里插入图片描述
废话不多数,直接上代码:

<view class="category-wrapper view">
  <navigation-bar type="'home'"></navigation-bar>
  <van-search value="{{ searchValue }}" shape="round" background="#eb4450" placeholder="请输入搜索关键词"
    placeholder-style="color: #737572;font-size: 26rpx" custom-class="search-input" class="search" />

  <view class="linear-back-line"></view>

  <view class="tab-wrapper">
    <scroll-view class="tab-scroll" scroll-y style="height: {{height.scrollMainHeight}}px;" scroll-with-animation
      scroll-top="{{tabScrollTop}}">
      <block wx:for="{{categoryData}}" wx:key="categoryId" wx:for-index="i" wx:for-item="category">
        <view class="tab-item {{activeTab === category.categoryId ? 'active' : ''}}" catch:tap="changeTab"
          data-id="{{category.categoryId}}">
          <text class="text">{{category.categoryName}}</text>
        </view>
      </block>
      <!-- 最后一个一定要加,让最后一个列表显示在剧中位置 -->
      <view style="min-height: {{height.scrollMainHeight/3}}px;"></view>
    </scroll-view>
    <scroll-view class="category-list-scroll" scroll-y style="height: {{height.scrollMainHeight}}px;"
      scroll-into-view="category_{{scrollToViewId}}" scroll-with-animation enhanced bindscroll="dragScroll">
      <block wx:for="{{categoryData}}" wx:key="categoryId" wx:for-index="i" wx:for-item="category">
        <view class="category-item" id="category_{{category.categoryId}}">
          <text class="text title">{{category.categoryName}}</text>
          <block wx:if="{{category.goods.length > 0}}">
            <block wx:for="{{category.goods}}" wx:key="goodId" wx:for-index="jdx" wx:for-item="good">
              <view class="good-item">
                <image class="image" src="{{good.img_src}}" mode="aspectFill" />
                <view class="good-info">
                  <text class="text title">{{good.goodName}}</text>
                  <view class="price-view">
                    <view class="price">
                      <text class="text unit"></text>
                      <text class="text num">{{good.price}}</text>
                    </view>
                    <i-icon class="icon" class-prefix="iconfont" name="icon-gouwuche" size="42"></i-icon>
                  </view>
                </view>
              </view>
            </block>
          </block>
          <view class="good-place" wx:else style="min-height: {{height.scrollMainHeight}}px;">
          </view>
        </view>
      </block>
    </scroll-view>
  </view>

  <view class="v-footer">
    <view class="shop-title">
      <view class="line left"></view>
      <text class="title text">店铺信息</text>
      <view class="line right"></view>
    </view>
    <i-footer class="footer-inner"></i-footer>
  </view>
</view>
const app = getApp();
const globalNavigator = app.globalData.navigator;

Page({
  options: {
    addGlobalClass: true,
  },
  data: {
    searchValue: "",
    categoryData: [
      {
        categoryId: 0,
        categoryName: "休闲零食",
        goods: [
          {
            goodId: "001",
            goodName: "至少我还有梦,也为你而感动...",
            img_src: "https://api-hmugo-web.itheima.net/pyg/banner1.png",
            price: "39.90",
          },
          {
            goodId: "002",
            goodName: "至少我还有梦,也为你而感动...",
            img_src: "https://api-hmugo-web.itheima.net/pyg/banner1.png",
            price: "39.90",
          },
          {
            goodId: "003",
            goodName: "至少我还有梦,也为你而感动...",
            img_src: "https://api-hmugo-web.itheima.net/pyg/banner1.png",
            price: "39.90",
          },
          {
            goodId: "004",
            goodName: "至少我还有梦,也为你而感动...",
            img_src: "https://api-hmugo-web.itheima.net/pyg/banner1.png",
            price: "39.90",
          },
          {
            goodId: "005",
            goodName: "至少我还有梦,也为你而感动...",
            img_src: "https://api-hmugo-web.itheima.net/pyg/banner1.png",
            price: "39.90",
          },
          {
            goodId: "006",
            goodName: "至少我还有梦,也为你而感动...",
            img_src: "https://api-hmugo-web.itheima.net/pyg/banner1.png",
            price: "39.90",
          },
          {
            goodId: "007",
            goodName: "至少我还有梦,也为你而感动...",
            img_src: "https://api-hmugo-web.itheima.net/pyg/banner1.png",
            price: "39.90",
          },
          {
            goodId: "008",
            goodName: "至少我还有梦,也为你而感动...",
            img_src: "https://api-hmugo-web.itheima.net/pyg/banner1.png",
            price: "39.90",
          },
        ],
      },
      {
        categoryId: 1,
        categoryName: "粮油调味",
        goods: [
          {
            goodId: "009",
            goodName: "至少我还有梦,也为你而感动...",
            img_src: "https://api-hmugo-web.itheima.net/pyg/banner1.png",
            price: "39.90",
          },
          {
            goodId: "010",
            goodName: "至少我还有梦,也为你而感动...",
            img_src: "https://api-hmugo-web.itheima.net/pyg/banner1.png",
            price: "39.90",
          },
          {
            goodId: "011",
            goodName: "至少我还有梦,也为你而感动...",
            img_src: "https://api-hmugo-web.itheima.net/pyg/banner1.png",
            price: "39.90",
          },
          {
            goodId: "012",
            goodName: "至少我还有梦,也为你而感动...",
            img_src: "https://api-hmugo-web.itheima.net/pyg/banner1.png",
            price: "39.90",
          },
          {
            goodId: "013",
            goodName: "至少我还有梦,也为你而感动...",
            img_src: "https://api-hmugo-web.itheima.net/pyg/banner1.png",
            price: "39.90",
          },
          {
            goodId: "014",
            goodName: "至少我还有梦,也为你而感动...",
            img_src: "https://api-hmugo-web.itheima.net/pyg/banner1.png",
            price: "39.90",
          },
          {
            goodId: "015",
            goodName: "至少我还有梦,也为你而感动...",
            img_src: "https://api-hmugo-web.itheima.net/pyg/banner1.png",
            price: "39.90",
          },
          {
            goodId: "016",
            goodName: "至少我还有梦,也为你而感动...",
            img_src: "https://api-hmugo-web.itheima.net/pyg/banner1.png",
            price: "39.90",
          },
        ],
      },
      {
        categoryId: 2,
        categoryName: "营养乳品",
        goods: [
          {
            goodId: "018",
            goodName: "至少我还有梦,也为你而感动...",
            img_src: "https://api-hmugo-web.itheima.net/pyg/banner1.png",
            price: "39.90",
          },
          {
            goodId: "019",
            goodName: "至少我还有梦,也为你而感动...",
            img_src: "https://api-hmugo-web.itheima.net/pyg/banner1.png",
            price: "39.90",
          },
        ],
      },
      {
        categoryId: 3,
        categoryName: "健康生活",
        goods: [
          {
            goodId: "019",
            goodName: "至少我还有梦,也为你而感动...",
            img_src: "https://api-hmugo-web.itheima.net/pyg/banner1.png",
            price: "39.90",
          },
          {
            goodId: "020",
            goodName: "至少我还有梦,也为你而感动...",
            img_src: "https://api-hmugo-web.itheima.net/pyg/banner1.png",
            price: "39.90",
          },
          {
            goodId: "021",
            goodName: "至少我还有梦,也为你而感动...",
            img_src: "https://api-hmugo-web.itheima.net/pyg/banner1.png",
            price: "39.90",
          },
          {
            goodId: "022",
            goodName: "至少我还有梦,也为你而感动...",
            img_src: "https://api-hmugo-web.itheima.net/pyg/banner1.png",
            price: "39.90",
          },
        ],
      },
      {
        categoryId: 4,
        categoryName: "家用电器",
        goods: [
          {
            goodId: "023",
            goodName: "至少我还有梦,也为你而感动...",
            img_src: "https://api-hmugo-web.itheima.net/pyg/banner1.png",
            price: "39.90",
          },
          {
            goodId: "024",
            goodName: "至少我还有梦,也为你而感动...",
            img_src: "https://api-hmugo-web.itheima.net/pyg/banner1.png",
            price: "39.90",
          },
          {
            goodId: "025",
            goodName: "至少我还有梦,也为你而感动...",
            img_src: "https://api-hmugo-web.itheima.net/pyg/banner1.png",
            price: "39.90",
          },
          {
            goodId: "026",
            goodName: "至少我还有梦,也为你而感动...",
            img_src: "https://api-hmugo-web.itheima.net/pyg/banner1.png",
            price: "39.90",
          },
        ],
      },
      {
        categoryId: 5,
        categoryName: "方便速食",
        goods: [
          {
            goodId: "027",
            goodName: "至少我还有梦,也为你而感动...",
            img_src: "https://api-hmugo-web.itheima.net/pyg/banner1.png",
            price: "39.90",
          },
          {
            goodId: "028",
            goodName: "至少我还有梦,也为你而感动...",
            img_src: "https://api-hmugo-web.itheima.net/pyg/banner1.png",
            price: "39.90",
          },
          {
            goodId: "029",
            goodName: "至少我还有梦,也为你而感动...",
            img_src: "https://api-hmugo-web.itheima.net/pyg/banner1.png",
            price: "39.90",
          },
          {
            goodId: "030",
            goodName: "至少我还有梦,也为你而感动...",
            img_src: "https://api-hmugo-web.itheima.net/pyg/banner1.png",
            price: "39.90",
          },
        ],
      },
      {
        categoryId: 6,
        categoryName: "运动出行",
        goods: [
          {
            goodId: "031",
            goodName: "至少我还有梦,也为你而感动...",
            img_src: "https://api-hmugo-web.itheima.net/pyg/banner1.png",
            price: "39.90",
          },
          {
            goodId: "032",
            goodName: "至少我还有梦,也为你而感动...",
            img_src: "https://api-hmugo-web.itheima.net/pyg/banner1.png",
            price: "39.90",
          },
          {
            goodId: "033",
            goodName: "至少我还有梦,也为你而感动...",
            img_src: "https://api-hmugo-web.itheima.net/pyg/banner1.png",
            price: "39.90",
          },
          {
            goodId: "034",
            goodName: "至少我还有梦,也为你而感动...",
            img_src: "https://api-hmugo-web.itheima.net/pyg/banner1.png",
            price: "39.90",
          },
        ],
      },
      {
        categoryId: 7,
        categoryName: "厨房用品",
        goods: [
          {
            goodId: "035",
            goodName: "至少我还有梦,也为你而感动...",
            img_src: "https://api-hmugo-web.itheima.net/pyg/banner1.png",
            price: "39.90",
          },
          {
            goodId: "036",
            goodName: "至少我还有梦,也为你而感动...",
            img_src: "https://api-hmugo-web.itheima.net/pyg/banner1.png",
            price: "39.90",
          },
          {
            goodId: "037",
            goodName: "至少我还有梦,也为你而感动...",
            img_src: "https://api-hmugo-web.itheima.net/pyg/banner1.png",
            price: "39.90",
          },
          {
            goodId: "038",
            goodName: "至少我还有梦,也为你而感动...",
            img_src: "https://api-hmugo-web.itheima.net/pyg/banner1.png",
            price: "39.90",
          },
        ],
      },
      {
        categoryId: 8,
        categoryName: "个护护理",
        goods: [
          {
            goodId: "039",
            goodName: "至少我还有梦,也为你而感动...",
            img_src: "https://api-hmugo-web.itheima.net/pyg/banner1.png",
            price: "39.90",
          },
          {
            goodId: "040",
            goodName: "至少我还有梦,也为你而感动...",
            img_src: "https://api-hmugo-web.itheima.net/pyg/banner1.png",
            price: "39.90",
          },
          {
            goodId: "0341",
            goodName: "至少我还有梦,也为你而感动...",
            img_src: "https://api-hmugo-web.itheima.net/pyg/banner1.png",
            price: "39.90",
          },
          {
            goodId: "042",
            goodName: "至少我还有梦,也为你而感动...",
            img_src: "https://api-hmugo-web.itheima.net/pyg/banner1.png",
            price: "39.90",
          },
        ],
      },
      {
        categoryId: 9,
        categoryName: "美妆造型",
        goods: [
          {
            goodId: "44",
            goodName: "至少我还有梦,也为你而感动...",
            img_src: "https://api-hmugo-web.itheima.net/pyg/banner1.png",
            price: "39.90",
          },
          {
            goodId: "45",
            goodName: "至少我还有梦,也为你而感动...",
            img_src: "https://api-hmugo-web.itheima.net/pyg/banner1.png",
            price: "39.90",
          },
          {
            goodId: "048",
            goodName: "至少我还有梦,也为你而感动...",
            img_src: "https://api-hmugo-web.itheima.net/pyg/banner1.png",
            price: "39.90",
          },
          {
            goodId: "046",
            goodName: "至少我还有梦,也为你而感动...",
            img_src: "https://api-hmugo-web.itheima.net/pyg/banner1.png",
            price: "39.90",
          },
        ],
      },
      {
        categoryId: 10,
        categoryName: "日用清洁",
        goods: [],
      },
      {
        categoryId: 11,
        categoryName: "家具百货",
        goods: [],
      },
      {
        categoryId: 12,
        categoryName: "床上用品",
        goods: [],
      },
      {
        categoryId: 13,
        categoryName: "礼包礼盒",
        goods: [],
      },
    ],
    activeTab: 0,
    tabScrollTop: 0,
    scrollToViewId: "",
    height: {
      scrollMainHeight: 0, // scroll-view 可视区域高度
      headerTotalHeight: 0, // (搜索框 + line 元素)总体高度
    },
    scrollViewParams: {},
  },
  async onShow() {
    app.getCurrentTabBar(1, this);
  },
  // 页面准备渲染
  onReady() {
    this.computScrollViewHeight();
    this.initScrollViewTopParams();
  },
  /**
   * 获取头部(搜索框 + line 元素)以及 screenHeight 屏幕的高度,用来计算出 scroll-view 滚动区域的真实高度
   */
  computScrollViewHeight() {
    wx.showLoading({
      title: "加载中...",
    });
    const query = wx.createSelectorQuery();
    var lineHeight = 0,
      searchInputBoxHeight = 0;

    // 获取 line 元素的高度
    query
      .select(".linear-back-line")
      .boundingClientRect(function (res) {
        lineHeight = res.height;
      })
      .exec();

    // 获取搜索框的高度
    query
      .select(".search")
      .boundingClientRect(function (res) {
        searchInputBoxHeight = res.height;
      })
      .exec();

    var that = this;
    var timer = null;
    timer = setTimeout(async () => {
      // 获取屏幕高度
      const { height: screenHeight } = await wx.getStorageSync("screenInfo");
      // 计算出 scroll-view 的可滚动区域高度 = 屏幕宽度 - (搜索框高度 + line 元素高度)
      that.setData(
        {
          height: {
            scrollMainHeight:
              screenHeight - (lineHeight + searchInputBoxHeight),
            headerTotalHeight: lineHeight + searchInputBoxHeight,
          },
        },
        () => {
          wx.hideLoading();
          clearTimeout(timer); // 赋值完成后清除定时器
        }
      );
    }, 250);
  },
  /**
   * 动态获取每个 scroll-view 中 view 的高度
   * 宏任务,防止节点未渲染完成而获取结果错误
   */
  initScrollViewTopParams() {
    wx.showLoading({
      title: "加载中...",
    });
    let timer = null;
    let that = this;
    timer = setTimeout(function () {
      var query = wx.createSelectorQuery();
      // scroll-view 每个滑动容器的 top 参数与 height 高度记录,用于判断
      let params = {
        heights: [], // 多个 view 容器的实时高度数组
        indexs: [], // 索引值
      };
      query
        .selectAll(".category-item")
        .boundingClientRect()
        .exec(function (res) {
          // 获取到每个 scroll-view 的区块元素,并遍历出它们各自的高度位置保存起来,在滚动的时候做位置判断
          for (let i = 0; i < res[0].length; i++) {
            params.heights.push(res[0][i].top);
            params.indexs[i] = i;
          }
          // 往最前面添加一个元素,代表第一个 tab 项(由于是自定义导航栏,所以需减去导航栏的高度),否则永远都会定位到第二项
          params.heights.unshift(
            params.heights[0] - globalNavigator.navigatorBarHeight - 15
          );
          that.setData(
            {
              scrollViewParams: {
                heights: params.heights.reverse(), // 将数据做一次反转,从上到下滚动时通过 数据.lenth - i 就是当前索引值
                indexs: params.indexs.reverse(),
              },
            },
            () => {
              wx.hideLoading();
              clearTimeout(timer); // 赋值完成后清除定时器
            }
          );
        });
    }, 250);
  },
  changeTab(e) {
    const data = e.currentTarget.dataset;
    this.setData({
      activeTab: data.id,
      scrollToViewId: data.id,
    });
  },
  // 滑动中【 scroll-view 容器页面在滚动】
  dragScroll(e) {
    let viewScrollTop = e.detail.scrollTop; // view 容器滑动的距离
    let viewHeights = this.data.scrollViewParams.heights;
    /**
     * 遍历之前页面渲染时存储的每个 view 容器的高度,并在每次滚动时对齐进行比对,如果超出了某个 height 则代表进入了这个容器的可视化区域,对应高亮 tab 项
     */
    for (let i = 0; i < viewHeights.length; i++) {
      if (viewScrollTop >= viewHeights[i] - 15) {
        // tab 选中选项的索引。假设移动到了第2个元素:length(14) - 13 = 1 【之前 reverse() 反转过的】
        let tabIndex = viewHeights.length - i - 1;
        let tabScrollTop = 0; // tab 导航栏的 scroll-top 值
        // 当滚动距离超过第 5 个tab 选项时,tab-sidebar 便也随着滚动
        if (tabIndex > 4) {
          tabScrollTop = (tabIndex - 4) * 106; // 106rpx : tab-item 的高度
        }
        this.setData({
          activeTab: tabIndex,
          tabScrollTop: tabScrollTop,
        });
        break;
      }
    }
  },
});

.tab-wrapper {
    display: flex;
    justify-content: space-between;
    align-items: flex-start;
    flex-direction: row;

    .tab-scroll {
      flex: 25%;
      padding-bottom: 36rpx;
      background-color: #f7f7f7;

      .tab-item {
        height: 106rpx;
        text-align: center;
        display: flex;
        justify-content: center;
        align-items: center;
        flex-direction: row;
        background-color: #f7f7f7;

        &.active {
          background-color: #fff;
          position: relative;

          .text {
            font-weight: 600;
            color: #000;
          }

          &::before {
            content: '';
            position: absolute;
            height: inherit;
            width: 10rpx;
            background-color: var(--themeColor);
            left: 0;
            top: 0;
            z-index: 1;
          }
        }

        .text {
          color: #333;
          font-size: 28rpx;
        }
      }
    }

    .category-list-scroll {
      flex: 75%;
      background-color: #fff;
      padding: 0rpx 26rpx 26rpx 26rpx;

      .category-item {
        display: flex;
        justify-content: center;
        align-items: center;
        flex-direction: column;

        &>.title {
          color: var(--subColor);
          font-size: 28rpx;
          align-self: flex-start;
          margin: 22rpx 0;
        }

        .good-item {
          width: 100%;
          height: 160rpx;
          margin-bottom: 26rpx;
          display: flex;
          justify-content: center;
          align-items: center;
          flex-direction: row;

          .image {
            width: 30%;
            height: inherit;
            object-fit: cover;
            border-radius: 8rpx;
          }

          .good-info {
            width: 70%;
            height: inherit;
            padding-left: 16rpx;
            box-sizing: border-box;
            display: flex;
            justify-content: space-between;
            align-items: center;
            flex-direction: column;

            .title {
              color: #333;
              font-size: 28rpx;
              text-overflow: ellipsis;
              overflow: hidden;
              white-space: nowrap;
              -o-text-overflow: ellipsis;
              width: 100%;
            }

            .price-view {
              width: 100%;
              display: flex;
              justify-content: space-between;
              align-items: center;
              flex-direction: row;

              .price {
                display: flex;
                justify-content: center;
                align-items: center;
                flex-direction: row;
                color: var(--themeColor);

                .unit {
                  font-size: 28rpx;
                  vertical-align: sub;
                  padding-top: 8rpx;
                }

                .num {
                  font-size: 34rpx;
                  font-weight: 500;
                }
              }
            }
          }
        }
      }
    }
  }

参考链接:https://blog.csdn.net/weixin_44936767/article/details/115710782

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值