
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>