惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

博客园 - Franky
N
Netflix TechBlog - Medium
宝玉的分享
宝玉的分享
Google DeepMind News
Google DeepMind News
腾讯CDC
G
Google Developers Blog
Martin Fowler
Martin Fowler
Microsoft Security Blog
Microsoft Security Blog
Recent Announcements
Recent Announcements
爱范儿
爱范儿
Engineering at Meta
Engineering at Meta
Microsoft Azure Blog
Microsoft Azure Blog
A
About on SuperTechFans
aimingoo的专栏
aimingoo的专栏
有赞技术团队
有赞技术团队
Jina AI
Jina AI
人人都是产品经理
人人都是产品经理
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
M
MIT News - Artificial intelligence
罗磊的独立博客
博客园 - 三生石上(FineUI控件)
美团技术团队
WordPress大学
WordPress大学
阮一峰的网络日志
阮一峰的网络日志

博客园 - 码间留白

uniapp打包的APP内嵌H5游戏切后台背景音乐停不下来的解决方案 微信小程序审核总被拒?掌握这套“极简闭环”方案,轻松通关,亲测有效! uni-app 项目鸿蒙原生适配实录:微信授权登录从"点击无反应"到真机跑通,条件编译、SDK 配置与那些官方文档没说清的坑 uniapp项目鸿蒙适配,rebuild 后同步原生工程的三种方案与分层决策 uniapp项目适配鸿蒙端没有自定义基座,调试和打包怎么办? 鸿蒙纯血系统适配实战记录:uView + Vue3 的 uni-app 项目适配鸿蒙 NEXT,一天搞定主功能运行到鸿蒙纯血系统 AI辅助前端开发实战指南:高效落地、避坑沉淀全流程 uni-app 集成网易云信通话插件:首页通话监听一直只有第一次有日志?从监听丢失到重复上报的完整治理之路 即时通信 IM 踩坑实录:群主拉人入群后无法收消息?一文讲透 10007 错误码 AI时代插件开发新玩法,分分钟抽离封装插件,你需要做的只是发布到插件市场 PhotoShop CS6在win11系统中界面字体很小,通过兼容性设置强制高 DPI 缩放即可解决 不会后端不用愁,Strapi解你忧——使用Qoder生成前端开发框架,技术栈React18 + Vite + ReactRouter6 + Axios + vw 适配 + Ant Design Mobile + Zustand + Strapi5 不会后端不用愁,Strapi解你忧——Strapi后台数据表创建及API联调测试,实现查询文章及关联的分类、标签、评论等表连接查询 不会后端不用愁,Strapi解你忧——搭建博客系统常用数据表设计 windows11系统如何安装多个版本的node,新旧项目切换再也不需要卸载重装了 不会后端不用愁,Strapi解你忧——前端使用Strapi就能实现常见的数据增删查改,分页、排序、模糊查询、时间范围等,不要后端也能自己建站 不会后端不用愁,Strapi解你忧——Windows 11 本地完整部署 Strapi + MySQL 8 实操攻略,从下载到配置启动全指南 不会后端不用愁,Strapi解你忧——Strapi v5.4设置中文语言展示 win11电脑浏览器无法上网但微信正常使用,通常是因为‌DNS解析失败‌,手动设置可靠的公共DNS服务器地址来解决问题 高效能CI/CD流水线设计:Jenkins与Docker的7个关键优化点 从零搭建 Docker + Jenkins CI/CD 流水线:实现项目自动化构建与部署 AI辅助开发——阿里Qoder重构vue2项目到vue3后继续对项目页面加载速度进行优化 vue3项目本地环境网路请求初始连接过久导致接口请求时间过长的问题处理 微信小程序内嵌vue项目实现页面自定义分享完整示例代码 JavaScript 数组高阶用法汇总(含浏览器+微信小程序WebView支持) 京东小程序报错APP-SERVICE-SDK:setStorageSync:fail vue3+ts项目自定义全局函数调用正常但IDE报异常类型ComponentPublicInstance上不存在属性“$showLoading" vue3+ts实现页面滚动位置的保存及恢复 到底是用vue2还是vue3好? css3过渡效果如何处理高度不确定的动态内容
vue项目实现Tab页面触底上拉切换下个Tab
码间留白 · 2025-11-17 · via 博客园 - 码间留白

lv_0_20251113151402 lv_0_20251113151518

如题,实现Tab页面触底上拉切换下一Tab的功能,关键流程:下滑触底——显示“上拉切换下一页”——继续上拉超过一定距离(如100px)——切换下一页,重置相关参数。

关键代码:

一. 处理滚动的业务逻辑封装为goodsListScroll.js,代码如下:

//触底上拉切换下一页相关代码
export default {
  props: {
    last_tab: false, //是否是最后一个tab
  },
  data() {
    return {
      startY: 0,
      maxScroll: 0, //最大可滚动距离
      showHint: false, //底部显示切换下一页提示条      
      touchStart: false, //触摸开始置为true,触发Tab切换后重置为false,终止handleTouchMove中的逻辑
      isScrolling: false, //显示“切换下一Tab”后触摸开始标记为true、切换Tab后重置为false
    };
  },
  beforeDestroy() {
    // 移除事件监听
    this.$refs.container.removeEventListener('touchstart', this.handleTouchStart);
    this.$refs.container.removeEventListener('touchmove', this.handleTouchMove);
  },
  methods: {
    //重新计算最大滚动值(因为内容可能变化),获取商品列表后调用
    calculateMaxScroll() {
      if (this.$refs.container) {
        const element = this.$refs.container;
        this.maxScroll = element.scrollHeight - element.clientHeight;
        //不可滚动页面直接显示        
        if (this.maxScroll === 0 && (this.active < this.subCateList.length - 1)) {
          this.showHint = true
        }
      }
    },
    //容器滚动监听事件
    handleScroll(event) {
      const element = event.target;
      this.scrollTop = element.scrollTop;

      // 计算滚动百分比
      this.scrollPercentage = this.maxScroll > 0 ?
        Math.min(100, Math.round((this.scrollTop / this.maxScroll) * 100)) :
        0;

      // 检查是否滚动到底部
      if (element.scrollHeight == element.clientHeight) return; //初始化的时候高度会相等
      const isAtBottom = element.scrollHeight - element.scrollTop <= element.clientHeight + 10;

      // 显示或隐藏提示
      if (this.active < this.subCateList.length - 1) this.showHint = isAtBottom;
    },
    /**
     * @param {Object} e
     * 触摸开始标记,必须在这里设置,切换tab后置为false,终止handleTouchMove
     */
    handleTouchStart(e) {
      this.touchStart = true
    },
    /**
     * 下滑触底上拉切换tab
     * 1. touchStart:触摸开始标志,切换tab后为false,终止代码执行
     * 2. isScrolling:页面上拉触底后置为true,同时记录当前位置作为滑动开始位置
     * 3. startY:计算触底上拉的初始位置,与实际触摸开始无关
     */
    handleTouchMove(e) {
      //1. 滑动过程中已经触发过Tab切换的就不再执行
      if (!this.touchStart) return;

      //2. 已触底且isScrolling为false时获取滑动的位置,并将isScrolling置为true
      if (this.showHint && !this.isScrolling) {
        this.isScrolling = true
        //3. 设置计算滑动距离的初始位置
        this.startY = e.touches[0].clientY;
      }

      //滑动过程中实时获取当前位置,并计算滑动距离
      const currentY = e.touches[0].clientY;
      const deltaY = this.startY - currentY; // 正值表示向上滑动

      // 只有向上滑动才处理
      if (deltaY > 0) {
        // 当向上滑动距离超过100px时触发切换
        if (deltaY > 100) {
          this.switchToNextTab();
        }
      }
    },
    //切换至下一个Tab
    switchToNextTab() {
      // 移除事件监听
      this.$refs.container.removeEventListener('touchstart', this.handleTouchStart);
      this.$refs.container.removeEventListener('touchmove', this.handleTouchMove);
      if (this.active < this.subCateList.length - 1) {
        this.active = this.active + 1
        //重置其他参数
        this.touchStart = false
        this.showHint = false;
        this.isScrolling = false;
        this.startY = 0;
        this.maxScroll = 0
      }
    }
  }
};

以上为完整代码,关键点有这些:

1. calculateMaxScroll():计算容器最大滚动值,在获取到商品列表数据后调用,不可滚动页面直接显示“上拉切换下一页”提示条;

2. handleScroll():商品列表容器滚动监听事件,针对可滚动页面在滑动到页面底部时,显示“上拉切换下一页”提示条;

3. handleTouchStart():滑动开始事件,将滑动开始标志touchStart置为true;

4. handleTouchMove():滑动过程监听,触底上拉超过一定距离,触发切换下一Tab的逻辑switchToNextTab();

5. switchToNextTab():切换下一Tab逻辑,移除滚动监听,所有相关参数重置。

代码中已经加了详细的注释,可以结合注释来看。

二. 页面相关代码:

<template>
  <div id="container" class="h100" ref="container" @scroll="handleScroll">
    <!--tab栏-->
    <van-tabs id="tabs" class="p_fixed top left w100" v-model="active" :title-active-color="redColor" :background="primaryColor" :color="primaryColor" title-inactive-color="#a8a8a8" swipe-threshold="4" sticky>
      <van-tab v-for="(item,index) in cateList" :title="item.title">
      </van-tab>
    </van-tabs>
    <!--商品列表栏-->
    <div class="goods_list w100 flex space_between" v-show="goodsList">
      <!--商品列表展示代码-->
    </div>
    <!--触达切换tab-->
    <div v-if="!last_tab" class="switch-hint p_fixed c_f flex center f12" :class="{ show: showHint,hide:!showHint&&startY==0 }">
      上拉切换下一页
    </div>
  </div>
</template>

<script>
  import { Tabs, Tab } from 'vant';
  import goodsListScroll from "@/assets/js/mixin/goodsListScroll.js"
  export default {
    name: "CateGoods",
    mixins: [goodsListScroll],
    components: {
      Nodata,
      [Tab.name]: Tab,
      [Tabs.name]: Tabs
    },
    data() {
      return {
        active: 0,
        cateList: '', //子分类列表
        goodsList: '', //瀑布流商品列表   
      }
    },
    watch: {
      //监听tab选择索引,获取对应的商品列表
      active(newValue, oldValue) {
        if (newValue > -1) {
          this.goodsList = ''
          let sub_rack_id = this.subCateList[this.active].sub_rack_id
          //获取商品列表
          this.getGoodsList(sub_rack_id, this.rack_id)
        }
      }
    },
    methods: {
      /**
       * @param {Object} rack_id
       * 获取商品列表
       */
      getGoodsList(rack_id, rack_parent_id) {
        //调用接口获取商品列表数据,这里代码省略

        //获取商品列表后监听页面滚动
        if (!this.last_tab) {
          // 确保DOM更新后计算最大滚动值
          this.$nextTick(() => {
            this.scrollTopFn()
            // 添加触摸事件监听
            this.$refs.container.addEventListener('touchstart', this.handleTouchStart, { passive: false });
            this.$refs.container.addEventListener('touchmove', this.handleTouchMove, { passive: false });
            //计算页面可以滚动距离
            this.calculateMaxScroll();
          });
        }
      } else {
        this.goodsList = []
      }
    });
  }
</script>

<style lang="scss" scoped>
  #container {
    overflow-y: scroll;
  }

  /*滚动触底上拉切换下一个Tab相关样式*/
  .switch-hint {
    bottom: 30px;
    left: 50%;
    transform: translateX(-50%);
    background: rgba(0, 0, 0, 0.7);
    padding: 12px 30px;
    border-radius: 25px;
    opacity: 0;
    transition: opacity 0.5s, transform 0.5s;
    pointer-events: none;
    z-index: 100;
  }

  .switch-hint.show {
    opacity: 1;
    transform: translateX(-50%) translateY(-10px);
  }

  .switch-hint.hide {
    transition: none;
  }
</style>

如上,页面+goodsListScroll即可实现Tab页面的触底上拉切换下一Tab的功能。

其中添加“上拉切换下一页”是为了优化用户体验,如果没有提示条,直接切换回显的有些突兀。

后记:

比较关键的点就是触底上拉切换Tab应该以触底上拉一定距离触发切换下一Tab的逻辑之后就应该结束,跟滑动是否结束无关。否则将会出现手指不松开一直上拉,可能会一直触发切换Tab,这个点要注意。