评论
【新手可会】10分钟!手搓uni-pp微信小程序左右滑动选项卡列表

【新手可会】10分钟!手搓uni-pp微信小程序左右滑动选项卡列表

如果我知道我是一只井底之蛙,那我还是井底之蛙吗

这成了一个系列,从选项卡中间点击闪屏,到选项卡点击动画,如今继续上压力;10分钟!手搓uni-pp微信小程序左右滑动选项卡列表

# 前提阅读

前端噩梦,当选项卡在页面中间如何处理点击后闪屏

【继续作死】 20行代码 uni-app 小程序选项卡重复点击 执行列表左侧滑入动画

思路

uni-app监听手势滑动,判断用户是在选项卡列表中有滑动。再使用滑动时间长差和滑动阈值优化事件,最后判断用户发动方向来实现不同滑动特效

核心

@touchstart="onTouchStart"

@touchmove="onTouchMove"

@touchend="onTouchEnd"

效果

代码

<template>
  <view>
    <!-- 选项卡 -->
    <view class="login-type-switch">
      <view 
        class="switch-item" 
        v-for="(item, index) in tab" 
        :class="{ active: active == index }"
        @tap="tabTap(index)"
      >
        {{ item }}
      </view>
    </view>

    <!-- 公告列表 -->
    <view 
      class="slide-container" 
      @touchstart="onTouchStart" 
      @touchmove="onTouchMove" 
      @touchend="onTouchEnd"
      :style="{'min-height': screenHeight - '260' + 'px'}"
      :class="{ 'slide-in-right': slideDirection === 'right' && isSlide, 'slide-in-left': slideDirection === 'left' && isSlide }"
    >
      <view v-for="(item, index) in oneDa" :key="index">
        <view 
          class="list-view" 
          v-if="item.type == active" 
          :class="{ 'fade-in': !isSlide }"
        >
          <view class="list-title">
            {{ item.title }}
          </view>
          <view class="list-content">
            <view class="list-content-item">
              发表于:{{ item.create_time }}
            </view>
            <view class="list-content-item">
              阅读:{{ item.number ? item.number : '0' }}
            </view>
          </view>
        </view>
      </view>
    </view>
  </view>
</template>

<script>
export default {
  data() {
    return {
      tab: ['系统通知', '产品推送'], //选项卡
      active: 0,
      
      isSlide: false, // 滑动动画
      touchStartX: 0, // 触摸开始的X坐标
      touchEndX: 0, // 触摸结束的X坐标
      touchStartTime: 0, // 触摸开始的时间
      touchEndTime: 0, // 触摸结束的时间
      swipeThreshold: 50, // 滑动阈值
      swipeTimeThreshold: 150, // 滑动时间阈值
      screenHeight: 0, //屏幕高度
      slideDirection: '', // 滑动方向
      
      oneDa: [
        {
          title: '今天是周一',
          create_time: '2025-04-22',
          number: 12,
          type: 0
        },
        {
          title: '今天是周二',
          create_time: '2025-04-22',
          number: 12,
          type: 0
        },
        {
          title: '今天是周三',
          create_time: '2025-04-22',
          number: 12,
          type: 1
        },
        {
          title: '今天是周四',
          create_time: '2025-04-22',
          number: 12,
          type: 1
        }
      ]
    }
  },
  onLoad() {
    uni.getSystemInfo({
      success: (res) => {
        this.screenHeight = res.screenHeight
      }
    });
    this.slideDirection = 'left';
    this.tabTap(0)
  },
  methods: {
    // 选项卡
    tabTap(e) {
      this.active = e;
      this.isSlide = false;
      this.getList();
    },
    getList() {
      // 动画结束后重置状态 可以丢到请求数据成功后执行
      setTimeout(() => {
        this.isSlide = true;
      }, 100);
    },
    
    // 触摸开始事件
    onTouchStart(e) {
      this.touchStartX = e.touches[0].clientX;
      this.touchStartTime = Date.now();
    },
    
    // 触摸移动事件
    onTouchMove(e) {
      this.touchEndX = e.touches[0].clientX;
    },
    
    // 触摸结束事件
    onTouchEnd() {
      this.touchEndTime = Date.now();
      const deltaX = this.touchEndX - this.touchStartX;
      if (this.touchEndX == 0) {
        return
      }
      const deltaTime = this.touchEndTime - this.touchStartTime;
      if (Math.abs(deltaX) > this.swipeThreshold && deltaTime > this.swipeTimeThreshold) {
        if (deltaX > 0) {
          // 右滑
          if (this.active > 0) {
            this.slideDirection = 'left';
            this.tabTap(this.active - 1);
          }
        } else {
          // 左滑
          if (this.active < this.tab.length - 1) {
            this.slideDirection = 'right';
            this.tabTap(this.active + 1);
          }
        }
      }
    }
  }
}
</script>

<style scoped>
.list-content {
  margin-top: 20rpx;
  display: flex;
  justify-content: space-between;
  align-items: center;
  font-size: 20rpx;
  color: #999;
}

.list-title {
  font-size: 36rpx;
  font-weight: bold;
  padding-bottom: 30rpx;
  border-bottom: 1px #f4f4f4 solid;
}

.list-view {
  padding: 30rpx 40rpx;
  border-radius: 20px;
  margin: 40rpx 20rpx;
  background-color: #ffffff;
  overflow: hidden;
  position: relative;
}

/* 登录方式切换样式 */
.login-type-switch {
  display: flex;
  padding: 30rpx 20rpx;
  justify-content: center;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
}

.switch-item {
  padding: 12rpx 24rpx;
  font-size: 26rpx;
  color: #999;
  position: relative;
  margin-right: 24rpx;
  white-space: nowrap;
  transition: all 0.3s ease;
}

.switch-item.active {
  color: #000;
  font-weight: 600;
}

.switch-item.active::after {
  content: "";
  position: absolute;
  bottom: 0.0625rem;
  left: 50%;
  transform: translateX(-50%);
  width: 0.5rem;
  height: 0.125rem;
  background: #000;
  border-radius: 0.0625rem;
}

.form-wrapper {
  position: relative;
  min-height: 400rpx;
}

/* 滑动动画 */
.slide-container {
  overflow: hidden;
  transition: all 0.3s ease-out;
}

.slide-in-right {
  animation: slideInRight 0.3s ease-out forwards;
}

.slide-in-left {
  animation: slideInLeft 0.3s ease-out forwards;
}

@keyframes slideInRight {
  from {
    transform: translateX(100%);
    opacity: 0;
  }
  to {
    transform: translateX(0);
    opacity: 1;
  }
}

@keyframes slideInLeft {
  from {
    transform: translateX(-100%);
    opacity: 0;
  }
  to {
    transform: translateX(0);
    opacity: 1;
  }
}

/* 添加淡入动画 */
.fade-in {
  opacity: 0;
  animation: fadeIn 3s ease-out forwards;
}
@keyframes fadeIn {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}

page {
  background-color: #f5f7fa;
}
</style>