【微信小程序】scroll-tab-view滑动选项卡的实现

微信小程序实现滑动选项卡

关于“如何做小程序的滑动式Tab选项卡,方法是什么”, 有一些人不是很理解,今天简单实现一下小程序的滑动选项卡。

效果如下图所示:

tab-view

这种设计结构其实在移动端是非常常见的设计,无论是h5 , app 还是 小程序,今天我们就用微信小程序来实现这个功能组件。

首先我们可以把这个分成上下两部分结构,上面可以点击切换的 tab 标签和下面能切换的视图。

首先我们分析上面,其实对于前端同学来说实现上面部分有很多方式,无非就是几个标签,激活的那个标签加个样式而已,但是我们这里要多考虑如果tab较多或者标签的字数比较长,做过多语言的同学可能知道,中文翻译成其他各种外语的时候,有些单词可能会很长,所以我们在设计的时候考虑到这种场景,我们最好把上面tab的设计成可以滑动的,这样无论多长或者多少tab我们都不怕我们的布局会打乱。所以我们可以用小程序的 scroll-view 来实现上面的布局。

然后我们接着看下面,是不是很眼熟,没错,就是我们常见的轮播图结构。我们可以用swiper来实现视图区域,知道了怎么布局,接下来我们只需要将上下两部分联动起来就ok了。

我们只需要在项目的 components 文件夹下新建一个 scroll-tab-view 的组件(RN 同款的组件也叫这个名字,暂且也就叫这个吧),然后我们来实现布局部分:

<!--components/scroll-tab-view/index.wxml-->
<view>
  <!-- 顶部tab -->
  <scroll-view scroll-x="true" enable-flex="true" class="scroll_h" scroll-left="{{scrollLeft}}" >
    <view 
      wx:for="{{tabs}}" 
      wx:key="item" 
      data-current="{{index}}" 
      style="width: {{tabWidth + 'px'}}"
      class="['scroll_item', {{currentIndex === index ? 'active' : ''}}]"
      mut-bind:tap="changeTabs">
      <view class="tabItem" style="width: {{tabWidth + 'px'}}">
        <text>{{item}}</text>
      </view>
    </view>
    <!-- 选中的下划线 -->
    <view class="select_line" style="width: {{tabWidth + 'px'}}; left: {{(currentIndex * tabWidth) + 'px'}}"></view>
  </scroll-view>
  <!-- tab对应视图区域 -->
  <view class="scroll_content"> 
    <swiper class="tab_content" duration="300" current="{{currentIndex}}" bindchange="switchTabView">
      <swiper-item wx:for="{{tabs}}" wx:key="item">
        <scroll-view 
          scroll-y="true" 
          class="scoll_h" 
          refresher-enabled="true"
          refresher-default-style="black"
          refresher-threshold="{{80}}"
          refresher-triggered="{{isRefresh}}"
          refresher-background="#c5c5c5"
          bindrefresherrefresh="onRefresh"
        >
          <view class="content_view">
            <!-- 此处循环渲染插槽,用来显示我们tab对应的列表内容 -->
            <slot name="{{index}}"></slot>
          </view>
        </scroll-view>
      </swiper-item>
    </swiper>
  </view>
</view>

我们把选中时的下划线加上动画,让它看起来更加的丝滑,而不是直接通过 active 样式来修改border来实现。然后我们来给它加上样式。

/* components/scroll-tab-view/index.wxss */
.scroll_h {
  position: relative;
  display: flex;
  height: 40px;
  line-height: 40px;
  border-bottom: 0.5px solid #e4e4e4;
}
.scroll_item {
  height: 40px;
  display: inline-block;
}
.tabItem {
  display: flex;
  justify-content: center;
  /* width: 80px; */
  height: 50rpx;
}

.active {
  /* border-bottom: 6rpx solid #0d7eff; */
  color: #0d7eff;
  font-weight: 600;
}

.select_line {
  position: absolute;
  bottom: 20px; // 根据自己情况定位选中线位置
  height: 6rpx;
  background-color: #0d7eff;
  transition: left 0.2s ease-in-out;
}

.scroll_content {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  top: 41px;
}

.tab_content {
  height: 100%;
}

.scoll_h {
  height: 100%;
}

.content_view {
  height: 100%;
  padding: 32rpx;
  background: #f1f1f1;
}

然后实现js部分:

// components/scroll-tab-view/index.js
Component({
  options:{
    multipleSlots:true   //这样就可以设置多个slot插槽
  },
  /**
   * 组件的属性列表
   */
  properties: {
    // tab选项卡标签的配置列表
    tabs: {
      type: Array,
      value: []
    },
    currentIndex: { // 当前选中的tab index
      type: [Number, String],
      value: 0
    },
    tabWidth: { // 每个tab设置的宽度
      type: Number,
      value: 80
    },
    isRefresh: {// 当前下拉刷新状态
      type: Boolean,
      value: false
    }
  },

  /**
   * 组件的初始数据
   */
  data: {
    scrollLeft: 0, // tab标题滚动条位置
  },

  /**
   * 组件的方法列表
   */
  methods: {
    changeTabs(e) {
      const currentIndex = e.currentTarget.dataset.current;
      this.triggerEvent('ChangeTab', {currentIndex});
      this.checkScrollBar(currentIndex);
    },
    // 切换视图
    switchTabView(e) {
      console.log('switchTabView', e);
      const currentIndex = e.detail.current;
      this.triggerEvent('ChangeTab', { currentIndex });
      this.checkScrollBar(currentIndex);
    },

    // 设置滚动条位置
    checkScrollBar(currentIndex) {
      // 获取屏幕宽度
      const { screenWidth } = wx.getSystemInfoSync();
      // 计算出完全显示在屏幕区的tab个数
      const $num = parseInt(screenWidth / this.data.tabWidth);
      if(currentIndex + 1 > $num) {
        this.setData({
          scrollLeft: $num * this.data.tabWidth
        })
      } else {
        this.setData({
          scrollLeft: 0
        })
      }
    },

    // 触发下拉刷新
    onRefresh() {
      this.triggerEvent('refresh', {
        current: this.data.currentIndex,
      })
    }
  }
})

这样我们就已经完成了我们组件的设计和实现,下面我们用该组件来实现我们上面图中的页面功能。

<!--pages/news/index.wxml-->
<view class="my_order">
  <scroll-tab-view 
    tabs="{{tabs}}" 
    currentIndex="{{currentIndex}}" 
    isRefresh="{{isRefresh}}"
    bindChangeTab="changeTab" 
    bindrefresh="refresh">
    
    <!-- 视图1 -->
    <view slot="0" class="list_contain">
      <view wx:for="{{list}}" wx:key="id" class="list_item">
        <view>{{item.title}}</view>
        <view class="list_date">{{item.date}}</view>
        <view class="list_content"> 
          <view class="list_img">
            <image src="{{item.url}}"></image>
          </view>
          <text>{{item.content}}</text>
        </view>
      </view>
    </view>

    <!-- 视图2 -->
    <view slot="1" class="list_contain">
      <!-- ... -->
    </view>

    <!-- 视图3 -->
    <view slot="2" class="list_contain">
      <!-- ... -->
    </view>

    <!-- 视图4 -->
    <view slot="3" class="list_contain">
      <!-- ... -->
    </view>
  </scroll-tab-view>
</view>

js:

// pages/news/index.js
Page({

  /**
   * 页面的初始数据
   */
  data: {
    tabs: ['健康', '情感', '职场', '育儿', '财经', '其他'],
    currentIndex: 0, // 当前选中的tab
    list: [], // 数据源
    showLoading: false, // loading
    isRefresh: false // 下拉刷新状态
  },

  /**
   * 生命周期函数--监听页面加载
   */
  onLoad: function (options) {
    this.getNewsList();
  },

  /**
   * 生命周期函数--监听页面显示
   * 自定义tabbar的部分,可以忽略
   */
  onShow: function () {
    if (typeof this.getTabBar === 'function' && this.getTabBar()) {
      const tabBar = this.getTabBar();
      let roleId = wx.getStorageSync('role');
      let selected = roleId ? 1 : 0;
      tabBar.setData({
        selected
      })
    }
  },
  
  // 切换tab,保存当前选中的index
  changeTab(e) {
    const { currentIndex } = e.detail;
    this.setData({ currentIndex });
  },

  // 获取列表数据
  getNewsList() {
    let _this = this;
    wx.request({
      url: 'https://miniapp.com/getNewsList',
      success(res) {
        const { list } = Reflect.get(res, 'data');
        _this.setData({
          showLoading: false,
          isRefresh: false, 
          list
        });
      },
      fail(e) {
        wx.showToast({
          title: e.errMsg,
        })
        _this.setData({ isRefresh: false })
      }
    })
  },

  /*
   * 列表下拉刷新(忽略)
   */
  refresh() {
    this.getNewsList();
  }
})

css:

/* pages/myOrder/myOrder.wxss */
.my_order {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: calc(env(safe-area-inset-bottom) + 48px);
}
.list_contain {
  height: 100%;
}
.list_item {
  display: flex;
  flex-direction: column;
  border-radius: 5px;
  background: #fff;
  margin-bottom: 10px;
  padding: 8px 16px;
}

.list_img {
  width: 80px;
  height: 50px;
  margin-right: 16px;
}

.list_img image {
  width: 80px;
  height: 50px;
}

.list_date {
  font-size: 24rpx;
  color: #aaaaaa;
}

.list_content {
  display: flex;
  flex-direction: row;
  margin-top: 20px;
}

json文件:

// 引入我们的组件
{
  "usingComponents": {
    "scroll-tab-view": "../../components/scroll-tab-view"
  },
  "navigationBarTitleText": "资讯"
}

组件就开发完成了,我们引入组件后要做的,只是在对应tab下面的 slot 中渲染我们自己的页面内容即可。

### 微信小程序 `scroll-view` 组件 `bindscroll` 事件失效的原因及解决方案 #### 原因分析 1. **高度未正确设置** 如果 `scroll-view` 的高度未被正确定义,则可能导致其内部滚动逻辑异常,从而使得 `bindscroll` 事件无法正常触发。通常情况下,当父容器的高度为动态百分比(如 `100%`),而子元素的实际高度未能计算出来时,就会发生此类问题[^2]。 2. **节流机制的影响** 微信小程序默认会对频繁触发的事件启用节流处理,以优化性能并减少资源消耗。然而,在某些场景下(例如快速滑动至边界位置),这种节流可能会干扰正常的绑定行为,导致 `bindscroll` 事件响应延迟甚至完全失效[^1]。 3. **样式冲突或属性配置不当** 若存在其他 CSS 样式与 `scroll-view` 属性相互影响的情况,也可能引发此现象。比如设置了固定宽度/高度却未开启对应方向上的滚动功能;或者遗漏了一些必要的参数声明(像 `scroll-x`, `scroll-y` 等)。另外需要注意的是,如果使用了框架工具生成页面结构的话,还需确认最终渲染出来的 HTML 是否符合预期[^5]。 4. **数据源更新滞后** 当通过 WXML 中的数据绑定来控制视图显示内容时,假如这些基础资料尚未加载完成就尝试操作 DOM ,那么很可能造成交互失败 。特别是涉及到列表项循环展示 (`wx:for`) 场景 下更需谨慎对待初始状态设定以及异步请求返回后的刷新时机把握等问题 [^4]. --- #### 解决方案 ##### 方法一:调整布局尺寸确保有效区域覆盖整个屏幕范围 可以通过显式指定单位像素值代替相对比例方式定义大小 , 并且加入额外的安全边距 来避免潜在溢出风险 : ```html <scroll-view scroll-y="true" style="height: calc(100vh - 80rpx); white-space: nowrap;" bindscroll="onScroll"> </scroll-view> ``` 此处利用了 CSS 计算表达式 `calc()` 函数扣除顶部导航栏占用空间 (假设高约为 80rpx),同时保留足够的可视面积供用户拖拽体验流畅度提升明显 . ##### 方法二:禁用内置节流器恢复实时反馈能力 针对高速移动过程中丢失回调通知的现象, 可直接在标签上附加特定标志位开关关闭自动限频策略: ```html <scroll-view scroll-x="true" bindscroll="handleScroll" throttle="{{false}}"> </scroll-view> ``` 上述代码片段中的 `throttle="{{false}}"` 即表示取消对该控件滚动作业实施频率限制措施 . 这样可以保证即使是在极端条件下也能及时接收到最新的坐标变动消息传递过来用于进一步业务逻辑判断处理 . ##### 方法三:验证依赖关系链路完整性排除外部因素干扰 仔细审查项目工程目录树下的关联文件是否存在拼写错误或者是版本兼容性差异引起的意外状况 发生 ; 同时也要留意是否有第三方插件库注入全局钩子函数篡改原始 API 表现形式 导致不可预见后果出现 : - 对于动态生成的内容区块部分特别要小心观察它们实际呈现效果是否偏离设计稿要求 ; - 针对网络请求获取远程服务器端接口回复数据流程则应该考虑增加超时保护机制防止长时间等待无果进而阻塞主线程运行效率下降 . 最后再次强调一点就是务必保持最新稳定发行版 SDK 客户端环境同步升级节奏以便享受官方团队持续改进修复各类已知缺陷带来的便利之处 [^3]. ##### 方法四:重构复杂嵌套层次简化整体架构便于维护管理 有时候过于繁琐冗长的标记语言组合反而会成为阻碍调试进展的一大障碍 所以建议适当拆分独立模块单独封装各自职责范围内所需实现的功能特性 而不是一股脑堆砌在一起难以理清头绪 如下面例子所示 将水平选项卡菜单抽离出去形成自定义组件后再引入主界面当中既清晰明了又方便后续扩展新特性支持: ```javascript // components/horizontal-tabs/index.js Component({ properties:{ tabBars:Array, tabIndex:Number }, methods:{ toggleTab:function(index){ this.setData({tabIndex:index}); this.triggerEvent('change',{index}) } } }) // pages/example/index.wxml <horizontal-tabs tab-bars="{{tabBars}}" tab-index="{{currentTabIndex}}" bind:change="handleChange"></horizontal-tabs> <scroll-view ... > ... </scroll-view> ``` 这样不仅提高了代码可读性和复用率还间接减少了不必要的耦合程度让开发者能够更加专注于核心算法层面的研究探索工作上去 [^5]. --- ### 总结 综上所述,微信小程序中 `scroll-view` 的 `bindscroll` 事件失效可能是由多种原因共同造成的,包括但不限于高度设置不合理、节流机制干扰、样式冲突或是数据源更新不同步等情况。因此,在排查具体问题时应综合考量以上各方面因素,并采取相应的解决对策加以应对。
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

small_Axe

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值