微信小程序左滑删除功能完整实现
功能特点
-
流畅的左滑删除交互:
- 向左滑动显示删除按钮
- 向右滑动关闭删除按钮
- 同时只能打开一个删除按钮
-
智能手势识别:
- 区分水平滑动和垂直滚动
- 垂直滚动时自动关闭删除按钮
-
完整的功能集:
- 添加新项目
- 删除单个项目
- 清空整个列表
- 空列表状态提示
-
美观的UI设计:
- 渐变背景和按钮
- 圆形头像
- 平滑的过渡动画
- 响应式布局
-
用户友好交互:
- 删除确认动画
- 清空列表确认弹窗
- 操作成功提示
使用说明
- 在微信开发者工具中创建新页面
pages/swipe-delete - 将上述文件放入对应目录
- 在
app.json的pages数组中添加页面路径 - 准备一张空列表提示图片(可替换
/images/empty.png)
文件结构
pages/
└── swipe-delete/
├── swipe-delete.js // 页面逻辑
├── swipe-delete.json // 页面配置
├── swipe-delete.wxml // 页面结构
└── swipe-delete.wxss // 页面样式
完整代码实现
1. swipe-delete.wxml
<!--pages/swipe-delete/swipe-delete.wxml-->
<view class="container">
<!-- 头部标题 -->
<view class="header">
<view class="title">消息列表</view>
<view class="subtitle">左滑项目可显示删除按钮</view>
</view>
<!-- 列表容器 -->
<scroll-view class="list-container" scroll-y>
<block wx:for="{{list}}" wx:key="id">
<view class="list-item">
<!-- 内容区域 -->
<view
class="item-content {{activeIndex === index ? 'active' : ''}}"
bindtouchstart="handleTouchStart"
bindtouchmove="handleTouchMove"
bindtouchend="handleTouchEnd"
data-index="{{index}}"
>
<view class="avatar">{{item.name.charAt(0)}}</view>
<view class="item-info">
<view class="item-title">{{item.name}}</view>
<view class="item-subtitle">{{item.message}}</view>
</view>
<view class="item-time">{{item.time}}</view>
</view>
<!-- 删除按钮 -->
<view
class="delete-btn {{activeIndex === index ? 'active' : ''}}"
bindtap="handleDelete"
data-index="{{index}}"
>
<text class="icon">删除</text>
</view>
</view>
</block>
<!-- 空列表提示 -->
<view wx:if="{{list.length === 0}}" class="empty-tip">
<image src="/images/empty.png" class="empty-image"></image>
<text class="empty-text">列表为空,请添加项目</text>
</view>
</scroll-view>
<!-- 底部操作按钮 -->
<view class="footer">
<button class="btn add-btn" bindtap="handleAddItem">
<text class="icon">+</text> 添加新项目
</button>
<button class="btn clear-btn" bindtap="handleClearAll">
<text class="icon">×</text> 清空列表
</button>
</view>
</view>
2. swipe-delete.wxss
/* pages/swipe-delete/swipe-delete.wxss */
.container {
display: flex;
flex-direction: column;
height: 100vh;
background-color: #f5f7fa;
}
.header {
background: linear-gradient(135deg, #4361ee, #3a0ca3);
color: white;
padding: 25rpx;
padding-top: 60rpx;
padding-bottom: 30rpx;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
}
.title {
font-size: 42rpx;
font-weight: bold;
margin-bottom: 10rpx;
}
.subtitle {
font-size: 26rpx;
opacity: 0.9;
}
.list-container {
flex: 1;
padding: 20rpx;
background-color: #f5f7fa;
}
.list-item {
position: relative;
height: 160rpx;
background: white;
border-radius: 16rpx;
margin-bottom: 20rpx;
overflow: hidden;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.05);
}
.item-content {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: white;
padding: 20rpx 30rpx;
display: flex;
align-items: center;
transition: transform 0.3s ease;
z-index: 2;
}
.item-content.active {
transform: translateX(-160rpx);
}
.avatar {
width: 100rpx;
height: 100rpx;
border-radius: 50%;
background: linear-gradient(45deg, #4361ee, #4cc9f0);
display: flex;
align-items: center;
justify-content: center;
color: white;
font-weight: bold;
font-size: 42rpx;
margin-right: 25rpx;
}
.item-info {
flex: 1;
}
.item-title {
font-weight: 600;
font-size: 34rpx;
margin-bottom: 10rpx;
color: #333;
}
.item-subtitle {
font-size: 28rpx;
color: #777;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
max-width: 400rpx;
}
.item-time {
font-size: 26rpx;
color: #999;
margin-right: 10rpx;
}
.delete-btn {
position: absolute;
top: 0;
right: -160rpx;
width: 160rpx;
height: 100%;
background: #e63946;
display: flex;
align-items: center;
justify-content: center;
color: white;
transition: all 0.3s ease;
z-index: 1;
}
.delete-btn.active {
right: 0;
}
.delete-btn .icon {
font-size: 32rpx;
}
.footer {
display: flex;
padding: 20rpx;
background: white;
border-top: 1rpx solid #eee;
}
.btn {
flex: 1;
height: 90rpx;
display: flex;
align-items: center;
justify-content: center;
border-radius: 45rpx;
font-size: 32rpx;
font-weight: 500;
margin: 0 15rpx;
transition: all 0.2s;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
}
.btn:active {
transform: translateY(4rpx);
box-shadow: 0 2rpx 6rpx rgba(0, 0, 0, 0.1);
}
.add-btn {
background: linear-gradient(135deg, #2ecc71, #27ae60);
color: white;
}
.clear-btn {
background: linear-gradient(135deg, #e74c3c, #c0392b);
color: white;
}
.empty-tip {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 100rpx 0;
}
.empty-image {
width: 300rpx;
height: 300rpx;
margin-bottom: 40rpx;
opacity: 0.6;
}
.empty-text {
font-size: 32rpx;
color: #999;
}
3. swipe-delete.js
// pages/swipe-delete/swipe-delete.js
Page({
data: {
list: [
{ id: 1, name: "张三", message: "拒绝加班!!!", time: "10:30" },
{ id: 2, name: "李四", message: "拒绝996!!!", time: "09:15" },
{ id: 3, name: "王五", message: "拒绝内卷!!!", time: "昨天" },
{ id: 4, name: "赵六", message: "拒绝搅乱市场价格!!!", time: "昨天" },
{ id: 5, name: "钱七", message: "拒绝拖欠工资!!!", time: "前天" }
],
activeIndex: -1, // 当前激活的列表项索引
startX: 0, // 触摸开始位置
startY: 0, // 触摸开始位置(Y轴)
isMoving: false // 是否正在滑动
},
// 处理触摸开始事件
handleTouchStart(e) {
const index = e.currentTarget.dataset.index;
const touch = e.touches[0];
// 记录触摸起始位置
this.setData({
startX: touch.clientX,
startY: touch.clientY,
isMoving: false
});
// 关闭之前打开的项目
if (this.data.activeIndex !== index) {
this.setData({ activeIndex: -1 });
}
},
// 处理触摸移动事件
handleTouchMove(e) {
const index = e.currentTarget.dataset.index;
const touch = e.touches[0];
// 计算滑动距离
const deltaX = touch.clientX - this.data.startX;
const deltaY = touch.clientY - this.data.startY;
// 如果Y轴滑动距离大于X轴,则判定为垂直滚动
if (Math.abs(deltaY) > Math.abs(deltaX)) {
this.setData({ isMoving: true });
return;
}
// 标记为水平滑动
this.setData({ isMoving: false });
// 向左滑动超过阈值(50px)时激活当前项
if (deltaX < -50) {
this.setData({ activeIndex: index });
}
// 向右滑动关闭当前项
if (deltaX > 20 && this.data.activeIndex === index) {
this.setData({ activeIndex: -1 });
}
},
// 处理触摸结束事件
handleTouchEnd() {
// 如果是垂直滚动,关闭所有删除按钮
if (this.data.isMoving) {
this.setData({ activeIndex: -1 });
}
},
// 删除项目
handleDelete(e) {
const index = e.currentTarget.dataset.index;
const list = this.data.list;
// 显示删除动画
this.setData({
activeIndex: index,
['list[' + index + '].deleting']: true
});
// 延迟执行删除操作
setTimeout(() => {
list.splice(index, 1);
this.setData({
list,
activeIndex: -1
});
wx.showToast({
title: '已删除',
icon: 'success',
duration: 1000
});
}, 300);
},
// 添加新项目
handleAddItem() {
const names = ["赵六", "钱七", "孙八", "周九", "吴十"];
const messages = [
"做人要有良心!!!",
"没钱你开什么公司!!!",
"没钱还要大小周!!!",
"加班为什么不给加班工资!!!",
"五险为什么也要拖欠!!!"
];
const newItem = {
id: Date.now(),
name: names[Math.floor(Math.random() * names.length)],
message: messages[Math.floor(Math.random() * messages.length)],
time: "刚刚"
};
// 添加到列表顶部
this.setData({
list: [newItem, ...this.data.list]
});
wx.showToast({
title: '已添加',
icon: 'success',
duration: 1000
});
},
// 清空列表
handleClearAll() {
if (this.data.list.length === 0) return;
wx.showModal({
title: '确认清空',
content: '确定要清空所有项目吗?',
success: (res) => {
if (res.confirm) {
this.setData({
list: [],
activeIndex: -1
});
wx.showToast({
title: '列表已清空',
icon: 'success',
duration: 1500
});
}
}
});
}
});
4. swipe-delete.json
{
"usingComponents": {},
"navigationBarTitleText": "左滑删除功能",
"navigationBarBackgroundColor": "#4361ee",
"navigationBarTextStyle": "white"
}