uniapp实现丝滑般的长按拖动排序

目录

效果展示

实现长按拖动排序功能

拖动过程中的位置交换逻辑

性能优化与边界处理

完整代码


效果展示

实现长按拖动排序功能

该代码实现了一个基于触摸事件的长按拖动排序功能,适用于移动端应用或小程序开发。通过触摸手势,用户可以拖动列表项重新排序,交互体验流畅。触摸事件捕获与处理 通过@touchstart@touchmove@touchend三个触摸事件监听用户操作。在touchstart时记录初始位置和当前拖拽项,touchmove阶段计算偏移量并实时更新位置,touchend时重置拖拽状态。positions对象保存每个元素的排序顺序和垂直位置坐标。draggingId标记当前拖拽的元素ID,isDragging是否正在拖拽中。

拖拽排序的实现主要依赖以下几个关键点:

  1. 触摸/鼠标事件监听

    • touchstart(触摸开始)

    • touchmove(触摸移动)

    • touchend(触摸结束)

  2. 位置计算与交换逻辑

    • 记录初始触摸位置(startY)

    • 计算拖拽偏移量(offsetY)

    • 检测是否需要交换位置(基于 itemHeight和阈值)

  3. 动画与过渡效果

    • 使用 transform: translateY() 实现平滑移动

    • 动态调整 zIndex确保拖拽元素在最上层

    • 拖拽时禁用 transition,结束时启用

拖动过程中的位置交换逻辑

checkSwapPosition方法处理拖动过程中的位置交换判断。当拖动项目超过相邻项目一半高度时触发交换:

checkSwapPosition(id, currentPos) {
  const threshold = this.itemHeight / 2
  const currentIndex = this.sortedList.findIndex(item => item.id === id)
  // 检查上方和下方元素是否满足交换条件
  // ...
}
 

swapItems方法实际执行交换操作,更新两个项目的排序顺序并重新计算所有项目位置:

swapItems(id1, id2) {
  const temp = this.positions[id1].order
  this.positions[id1].order = this.positions[id2].order
  this.positions[id2].order = temp
  this.updatePositions()
}
 

性能优化与边界处理

代码通过touchmove.prevent阻止默认滚动行为,避免与页面滚动冲突。拖动结束时在onTouchEnd中重置所有状态:

onTouchEnd() {
  if (!this.isDragging) return
  this.isDragging = false
  this.offsetY = 0
  this.draggingId = null
}
 

初始化时使用this.$set确保positions的响应性,避免直接赋值导致的数据更新问题。 

完整代码

<template>
	<view class="list">
		<view class="title">长按拖动模块排序</view>
		<view
				class="list-item" 
				v-for="item in sortedList" 
				:key="item.id"
				:style="{
					transform: draggingId === item.id ? `translateY(${offsetY}px)` : '',
					transition: isDragging ? 'none' : 'transform 0.2s ease',
					zIndex: draggingId === item.id ? 99 : 1,
					backgroundColor: draggingId === item.id ? '#f5f5f5' : '#fff'
				}"
				@touchstart="onTouchStart($event, item.id)"
				@touchmove.prevent="onTouchMove($event, item.id)"
				@touchend="onTouchEnd"
				@touchcancel="onTouchEnd"
			>
				<image class="item-image" src="../../static/images/sorting.png"></image>
				<text>{{ item.name }}</text>
		</view>
	</view>
</template>

<script>
export default {
	data() {
		return {
			list: [
				{ id: 1, name: '1号' },
				{ id: 2, name: '2号' },
				{ id: 3, name: '3号' },
				{ id: 4, name: '4号' },
				{ id: 5, name: '5号' },
			],
			positions: {},
			startY: 0,
			offsetY: 0,
			draggingId: null,
			isDragging: false,
			itemHeight: 55
		}
	},
	computed: {
		sortedList() {
			return [...this.list].sort((a, b) => (this.positions[a.id]?.order || 0) - (this.positions[b.id]?.order || 0))
		}
	},
	mounted() {
		this.initPositions()
	},
	methods: {
		initPositions() {
			this.list.forEach((item, index) => {
				this.$set(this.positions, item.id, {
				order: index,
				y: index * this.itemHeight
				})
			})
		},
		onTouchStart(event, id) {
			this.isDragging = true
			this.draggingId = id
			this.startY = event.touches[0].clientY
		},
		onTouchMove(event, id) {
			if (!this.isDragging || this.draggingId !== id) return
			const touchY = event.touches[0].clientY
			this.offsetY = touchY - this.startY
			// 计算当前拖拽位置
			const currentPos = this.positions[id].y + this.offsetY
			// 检查是否需要交换
			this.checkSwapPosition(id, currentPos)
		},
    
		checkSwapPosition(id, currentPos) {
			const threshold = this.itemHeight / 2
			const currentIndex = this.sortedList.findIndex(item => item.id === id)
			const currentItem = this.positions[id]
			// 检查上方元素
			if (currentIndex > 0) {
				const prevItem = this.positions[this.sortedList[currentIndex - 1].id]
				if (currentPos < prevItem.y + threshold) {
					this.swapItems(id, this.sortedList[currentIndex - 1].id)
					return
				}
			}
			// 检查下方元素
			if (currentIndex < this.sortedList.length - 1) {
				const nextItem = this.positions[this.sortedList[currentIndex + 1].id]
				if (currentPos > nextItem.y - threshold) {
					this.swapItems(id, this.sortedList[currentIndex + 1].id)
					return
				}
			}
		},
		swapItems(id1, id2) {
			// 交换order值
			const temp = this.positions[id1].order
			this.positions[id1].order = this.positions[id2].order
			this.positions[id2].order = temp
			// 更新位置信息
			this.updatePositions()
			// 重置偏移量
			this.offsetY = 0
			this.startY += (this.positions[id1].y - this.positions[id2].y)
		},
		updatePositions() {
			this.sortedList.forEach((item, index) => {
				this.positions[item.id].y = index * this.itemHeight
			})
		},
		onTouchEnd() {
			if (!this.isDragging) return
			this.isDragging = false
			this.offsetY = 0
			this.draggingId = null
		}
	}
}
</script>

<style lang="scss" scoped>
.list {
	padding-top: 24rpx;
}

.title{
	margin-left: 24rpx;
	margin-bottom: 24rpx;
	font-size: 28rpx;
	font-weight: 400;
	letter-spacing: 0px;
	color: rgba(138, 138, 138, 1);
}

.list-item{
	display: flex;
	align-items: center;
	gap: 24rpx;
	border-top: 0.5rpx solid rgba(230, 230, 230, 1);
	border-bottom: 0.5rpx solid rgba(230, 230, 230, 1);
	padding: 31rpx 24rpx;
	.item-image{
		width: 32rpx;
		height: 32rpx;
	}
	.item-text{
		font-size: 28px;
		font-weight: 400;
		letter-spacing: 0px;
		color: rgba(48, 63, 82, 1);
	}
}
.list-item:last-child{
	// border-bottom: 2rpx solid rgba(230, 230, 230, 1);
}

</style>
<think>好的,用户让我帮忙写一个uniapp的九宫格拖动排序功能。首先,我需要确认用户的使用场景和技术基础。可能用户是前端开发者,或者至少对uniapp有一定了解,想要实现一个常见的拖拽排序效果。九宫格通常用于图片、图标等的排列,拖动排序可以让用户自定义顺序,这在很多应用中都有用到。 接下来,我需要考虑uniapp的特点。uniapp支持多端发布,所以代码需要兼容不同平台,尤其是H5和小程序。在实现拖拽排序时,不同平台的触摸事件处理可能有所不同,可能需要使用跨平台的解决方案。 用户可能希望的是一个简单易懂的示例代码,可以直接复制使用,或者作为参考进行修改。因此,我应该提供一个基于uniapp的基本结构,使用view组件和CSS来布局九宫格,然后处理触摸事件来实现拖拽排序。 首先,九宫格的布局可以使用flex布局,每个格子设置为固定宽度,并且可拖动。数据方面,可以使用一个数组来存储每个格子的信息和顺序。每个格子需要唯一的标识,比如id或者index。 然后,拖拽实现需要考虑触摸事件:touchstart、touchmove、touchend。在touchstart时记录当前拖动的元素索引和初始位置;在touchmove时计算移动距离,并实时更新元素的位置;在touchend时调整数组顺序,完成排序。 需要注意的是,在移动过程中,可能需要动态改变元素的样式,比如平移位置,使用transform: translateX/Y。同时,为了防止拖动过程中页面滚动,可能需要在touchmove事件中阻止默认行为。 另外,动画效果可以提升用户体验,比如在排序完成后添加过渡动画。这可以通过CSS的transition属性来实现。 还需要考虑性能问题,尤其是在移动端,频繁的DOM操作可能导致卡顿。因此,应该尽量减少不必要的重绘,比如使用绝对定位或者translate来移动元素,而不是直接修改布局属性。 用户可能遇到的问题包括:拖拽时元素位置计算不准确,拖动过程中其他元素的位置调整,以及不同平台的兼容性。在示例代码中需要处理这些情况,比如通过计算触摸位置和元素的位置关系,动态交换数组中的元素顺序。 最后,我需要测试示例代码,确保在H5和小程序上都能正常工作。如果有平台差异,可能需要添加条件编译或者使用uniapp的API来处理事件。 总结下来,步骤大概是:创建九宫格布局,绑定数据,处理触摸事件,实现拖拽逻辑,更新数据并触发视图更新。同时,提供CSS样式和过渡效果,使拖动更平。</think>以下是一个使用 UniApp 实现的九宫格拖动排序示例代码,支持 H5 和小程序平台: ```html <template> <view class="container"> <view class="grid-container" @touchmove.prevent="handleTouchMove" <!-- 阻止默认滚动行为 --> > <view v-for="(item, index) in gridList" :key="item.id" class="grid-item" :style="getItemStyle(index)" @touchstart="handleTouchStart(index, $event)" @touchmove.capture="handleTouchMove" @touchend="handleTouchEnd" > {{ item.text }} </view> </view> </view> </template> <script> export default { data() { return { gridList: Array(9).fill().map((_, i) => ({ id: i + 1, text: i + 1 })), draggingIndex: -1, // 当前拖拽的索引 startY: 0, // 触摸起始Y坐标 currentY: 0, // 当前触摸Y坐标 offsetY: 0 // Y轴偏移量 } }, methods: { // 触摸开始事件 handleTouchStart(index, event) { this.draggingIndex = index this.startY = event.touches[0].clientY this.currentY = this.startY }, // 触摸移动事件 handleTouchMove(event) { if (this.draggingIndex === -1) return this.currentY = event.touches[0].clientY this.offsetY = this.currentY - this.startY // 实时交换位置 const newIndex = this.calculateNewIndex() if (newIndex !== this.draggingIndex) { this.swapItems(this.draggingIndex, newIndex) this.draggingIndex = newIndex this.startY = this.currentY } }, // 触摸结束事件 handleTouchEnd() { this.draggingIndex = -1 this.offsetY = 0 }, // 计算新位置索引 calculateNewIndex() { const rowCount = 3 // 每行3列 const delta = Math.round(this.offsetY / (this.gridHeight + 10)) // 10是间距 const newIndex = this.draggingIndex + delta * rowCount return Math.max(0, Math.min(newIndex, this.gridList.length - 1)) }, // 交换数组元素 swapItems(oldIndex, newIndex) { const temp = [...this.gridList] ;[temp[oldIndex], temp[newIndex]] = [temp[newIndex], temp[oldIndex]] this.gridList = temp }, // 动态样式计算 getItemStyle(index) { const isDragging = index === this.draggingIndex return { transform: isDragging ? `translateY(${this.offsetY}px)` : 'none', transition: isDragging ? 'none' : 'transform 0.3s', zIndex: isDragging ? 999 : 1 } } } } </script> <style scoped> .container { padding: 20px; } .grid-container { display: flex; flex-wrap: wrap; gap: 10px; } .grid-item { width: calc((100% - 20px) / 3); /* 3列布局 */ height: 100px; background-color: #f0f0f0; border-radius: 8px; display: flex; justify-content: center; align-items: center; font-size: 24px; touch-action: none; /* 禁用默认触摸行为 */ position: relative; box-shadow: 0 2px 4px rgba(0,0,0,0.1); } </style> ``` ### 核心实现原理: 1. **触摸事件处理**:通过 `@touchstart`、`@touchmove`、`@touchend` 实现拖动交互 2. **位置计算**:根据手指移动距离计算新位置,每行3列布局(可通过修改rowCount变量调整列数) 3. **数组交换**:通过交换数组元素实现数据更新,驱动视图变化 4. **动画效果**:使用 CSS transform 实现移动效果,transition 添加动画过渡 ### 功能扩展建议: 1. 增加 **X轴拖动支持**:修改坐标计算逻辑为二维计算 2. 添加 **震动反馈**:在小程序中使用 `wx.vibrateShort()` 3. 实现 **跨行拖动**:改进 `calculateNewIndex` 方法 4. 增加 **拖拽占位效果**:在交换位置时显示占位符 5. 支持 **本地存储**:使用 uni.setStorageSync 保存排序结果 如果需要更复杂的交互效果(如拖拽缩放、多选拖拽等),可以考虑使用第三方库如: - `sortablejs`(需通过uni-app的renderjs实现) - `uniapp-draggable` 等专门为uni-app优化的库 实际开发中需要注意不同平台的触摸事件差异,建议使用 `uni.getSystemInfoSync()` 进行平台判断和适配。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值