uni-app x 左右滑动效果

scroll-view组件:

这个组件实现不了

swiper组件 :

这个组件实现不完美,这个不能实现左边多一个菜单只显一半的效果,只能显示完整一个,滑动出现下一个

基于 Touch 事件的滑动菜单实现:

复制代码
<template>
  <view class="container">
    <!-- 基于 Touch 事件的滑动菜单 -->
    <view 
      class="touch-menu"
      @touchstart="onTouchStart"
      @touchmove="onTouchMove"
      @touchend="onTouchEnd"
    >
      <view 
        class="menu-list"
        :style="{ transform: `translateX(${translateX}px)`, transition: isDragging ? 'none' : 'transform 0.3s ease' }"
      >
        <view 
          v-for="(item, index) in menus" 
          :key="index"
          class="menu-item"
          :class="{ active: activeIndex === index }"
          @click="selectMenu(index)"
        >
          <text class="menu-text">{{ item }}</text>
        </view>
      </view>
    </view>

    <!-- 内容区域 -->
    <view class="content">
      <text class="content-text">当前选中菜单: {{ menus[activeIndex] }}</text>
      <view class="description">
        <text class="desc-text">按住菜单区域左右滑动可以查看更多菜单项</text>
      </view>
    </view>
  </view>
</template>

<script>
export default {
  data() {
    return {
      menus: [
        '首页', '推荐', '热门', '视频', '音乐', 
        '游戏', '体育', '科技', '财经', '汽车',
        '房产', '时尚', '美食', '旅游', '健康'
      ],
      activeIndex: 0,
      translateX: 0,
      startX: 0,
      currentTranslateX: 0,
      isDragging: false,
      screenWidth: 375,
      itemWidth: 80, // 菜单项宽度
      itemMargin: 10 // 菜单项间距
    }
  },
  
  onReady() {
    const systemInfo = uni.getSystemInfoSync()
    this.screenWidth = systemInfo.windowWidth
    console.log('屏幕宽度:', this.screenWidth)
  },
  
  computed: {
    // 计算最大可滑动距离(负值)
    maxTranslateX() {
      const totalMenuWidth = this.menus.length * this.itemWidth + (this.menus.length - 1) * this.itemMargin
      const containerWidth = this.screenWidth
      
      console.log('总菜单宽度:', totalMenuWidth, '容器宽度:', containerWidth)
      
      // 如果总宽度小于容器宽度,则不需要滑动
      if (totalMenuWidth <= containerWidth) {
        return 0
      }
      
      // 计算最大滑动距离,确保最后一个菜单项可以完全显示
      const maxTranslate = containerWidth - totalMenuWidth
      console.log('最大滑动距离:', maxTranslate)
      return maxTranslate
    }
  },
  
  methods: {
    onTouchStart(e) {
      this.isDragging = true
      this.startX = e.touches[0].clientX
      this.currentTranslateX = this.translateX
    },
    
    onTouchMove(e) {
      if (!this.isDragging) return
      
      const currentX = e.touches[0].clientX
      const diffX = currentX - this.startX
      
      let newTranslateX = this.currentTranslateX + diffX
      
      // 限制滑动边界,添加一些弹性效果
      // 向右滑动限制:可以稍微超过0(最左边),但会有回弹效果
      if (newTranslateX > 50) {
        newTranslateX = 50 + (newTranslateX - 50) * 0.3 // 弹性系数
      }
      // 向左滑动限制:可以稍微超过maxTranslateX(最右边),但会有回弹效果
      else if (newTranslateX < this.maxTranslateX - 50) {
        newTranslateX = (this.maxTranslateX - 50) + (newTranslateX - (this.maxTranslateX - 50)) * 0.3 // 弹性系数
      }
      
      this.translateX = newTranslateX
    },
    
    onTouchEnd() {
      this.isDragging = false
      
      // 滑动结束后确保不会显示不完整的菜单项
      this.adjustToNearestItem()
      
      // 确保最终位置在有效范围内
      setTimeout(() => {
        if (this.translateX > 0) {
          this.translateX = 0
        } else if (this.translateX < this.maxTranslateX) {
          this.translateX = this.maxTranslateX
        }
      }, 300) // 等待过渡动画完成
    },
    
    // 调整到最近的完整菜单项
    adjustToNearestItem() {
      // 如果已经在边界位置,直接返回
      if (this.translateX >= 0) {
        this.translateX = 0
        return
      }
      
      if (this.translateX <= this.maxTranslateX) {
        this.translateX = this.maxTranslateX
        return
      }
      
      // 计算当前显示的第一个完整菜单项的索引
      const visibleStart = -this.translateX
      const itemTotalWidth = this.itemWidth + this.itemMargin
      
      // 计算应该对齐到哪个菜单项
      const targetIndex = Math.round(visibleStart / itemTotalWidth)
      let targetTranslateX = -targetIndex * itemTotalWidth
      
      // 确保目标位置不会超出边界
      if (targetTranslateX > 0) {
        targetTranslateX = 0
      } else if (targetTranslateX < this.maxTranslateX) {
        targetTranslateX = this.maxTranslateX
      }
      
      // 确保最后一个菜单项可以完全显示
      const lastItemOffset = -(this.menus.length - 1) * itemTotalWidth
      const minTranslateX = this.screenWidth - (this.menus.length * itemTotalWidth)
      
      if (targetTranslateX < minTranslateX) {
        targetTranslateX = minTranslateX
      }
      
      this.translateX = targetTranslateX
    },
    
    selectMenu(index) {
      this.activeIndex = index
      
      // 计算选中菜单项的位置
      const itemTotalWidth = this.itemWidth + this.itemMargin
      const itemCenter = index * itemTotalWidth + this.itemWidth / 2
      const screenCenter = this.screenWidth / 2
      
      let targetTranslateX = screenCenter - itemCenter
      
      // 计算最小和最大滑动距离
      const minTranslateX = this.screenWidth - (this.menus.length * itemTotalWidth)
      
      // 确保目标位置不会超出边界
      if (targetTranslateX > 0) {
        targetTranslateX = 0
      } else if (targetTranslateX < minTranslateX) {
        targetTranslateX = minTranslateX
      }
      
      this.translateX = targetTranslateX
    }
  }
}
</script>

<style>
.container {
  flex: 1;
  background-color: #f8f8f8;
}

.touch-menu {
  width: 100%;
  height: 60px;
  background-color: white;
  border-bottom: 1px solid #eeeeee;
  overflow: hidden;
  position: relative;
}

.menu-list {
  flex-direction: row;
  height: 60px;
  align-items: center;
  padding-left: 10px;
  padding-right: 10px;
  /* 添加padding确保边缘菜单项可以完整显示 */
  position: absolute;
  left: 0;
  top: 0;
  white-space: nowrap;
}

.menu-item {
  width: 80px;
  height: 40px;
  justify-content: center;
  align-items: center;
  border-radius: 20px;
  margin-right: 10px;
  background-color: #f0f0f0;
  flex-shrink: 0;
}

/* 最后一个菜单项不需要右边距 */
.menu-item:last-child {
  margin-right: 0;
}

.menu-item.active {
  background-color: #007aff;
}

.menu-text {
  font-size: 14px;
  color: #333333;
}

.menu-item.active .menu-text {
  color: white;
  font-weight: bold;
}

.content {
  flex: 1;
  padding: 20px;
  align-items: center;
}

.content-text {
  font-size: 18px;
  color: #333333;
  margin-bottom: 20px;
}

.description {
  padding: 15px;
  background-color: #e8f4ff;
  border-radius: 8px;
  width: 100%;
}

.desc-text {
  font-size: 14px;
  color: #007aff;
  text-align: center;
}
</style>
相关推荐
居安思危_Ho16 小时前
RK平台Uniapp自启动缓存问题解决
android·缓存·uni-app·rk平台·uniapp资源文件
你真的可爱呀17 小时前
uniapp学习【项目创建+项目结构解析】
学习·uni-app
小小弯_Shelby17 小时前
uniApp App内嵌H5打开内部链接,返回手势(左滑右滑页面)会直接关闭H5项目
前端·uni-app
卷Java18 小时前
百度AI车牌识别配置指南
java·开发语言·百度·uni-app·dubbo·微信公众平台
游戏开发爱好者820 小时前
苹果iOS26系统升级:液态玻璃与智能功能全解析
macos·ios·小程序·uni-app·objective-c·cocoa·iphone
2501_915918411 天前
iOS 26 App 性能测试|性能评测|iOS 26 性能对比:实战策略
android·macos·ios·小程序·uni-app·cocoa·iphone
用户904706683572 天前
uniapp Vue3版本,用pinia存储持久化插件pinia-plugin-persistedstate对微信小程序的配置
前端·uni-app
乔冠宇2 天前
uniapp创建ts项目tsconfig.json报错的问题
uni-app
细节控菜鸡2 天前
【2025最新】uniapp 中基于 request 封装实现多文件上传完整指南
uni-app