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>
相关推荐
游戏开发爱好者814 小时前
iOS 商店上架全流程解析 从工程准备到审核通过的系统化实践指南
android·macos·ios·小程序·uni-app·cocoa·iphone
toooooop814 小时前
Vuex 中 state、mutations 和 actions 的原理和写法
前端·javascript·uni-app
林_xi15 小时前
uniapp使用@uni-ku/root插件实现全局组件
前端·uni-app
计算机毕设定制辅导-无忧学长15 小时前
基于uni-app的“民族风韵”特色购物小程序
uni-app
一个处女座的程序猿O(∩_∩)O15 小时前
UniApp 生命周期全解析:从应用到页面,再到组件的完美协奏曲
前端·uni-app
你听得到1117 小时前
Web前端们!我用三年亲身经历,说说从 uniapp 到 Flutter怎么转型的,这条路我爬过,坑我踩过
前端·flutter·uni-app
IT 前端 张19 小时前
Uniapp全局显示 悬浮组件/无需单页面引入
前端·javascript·uni-app
行云流水62621 小时前
uniapp h5图片长按隐藏默认菜单弹出
前端·javascript·uni-app
对角1 天前
用 Gemini 3 复刻了 X 上爆火的复古拍立得,AI 也能写小程序了?
前端·uni-app·ai编程
2501_916008891 天前
Objective-C 测试(OC 测试)指南 从单元测试到性能调优的多工具协同方法
android·ios·小程序·https·uni-app·iphone·webview