📱 背景
产品要求实现左滑删除的功能,动画是挺丝滑的,但是我觉得牺牲了一定理解性,用户找个删除可能找半天,不过仁者见仁智者见智。
技术栈
- 开发框架:原生小程序
- 核心组件:
🌟 场景1: 固定高度列表项
处理思路
将最右边的删除按钮隐藏(位置在可视区域外),只有通过滑动才能使其进入视野。
WXML代码示例
<view class="stock-list">
<movable-area class="stock-list-item" wx:for="{{list}}" wx:key="id">
<movable-view
direction="horizontal"
class="drag-wrapper"
inertia
>
<view class="stock-list-item-left">
<!-- 序号 -->
</view>
<view class="stock-list-item-center">
<!-- 中间信息 -->
</view>
<view class="stock-list-item-right">
<!-- 右侧部分 -->
<image alt="编辑按钮"/>
<view class="delete-btn" catch:tap="handleDelete" data-id="{{item.id}}">
<image alt="删除按钮"/>
</view>
</view>
</movable-view>
</movable-area>
</view>
注意事项
- 根组件高度设置:最顶层的根组件不能固定死高度,否则内部的
movable-area
会被挤压。建议使用min-height: 90vh
。 - 指定尺寸:
movable-area
需要指定height
和width
,以避免被纵向或横向挤压。
- 最大拖动距离:横向可以拖动的最大距离取决于
movable-view
的宽度。
🎯 场景2:动态高度列表项
最右侧删除按钮相对于 movable-view
做绝对定位,同时监听其高度,对高度进行动态同步。通过监听 touch
事件判断用户是误触还是左右滑动,并通过 change
事件处理移动事件的结果,对不同滑动距离做不同操作。
WXML代码示例
<movable-area
class="movalble-content-wrapper"
style="margin-bottom: {{bottomMargin}}"
catch:tap="handleTap"
data-item="{{listItem}}"
>
<movable-view
direction="horizontal"
class=" drag-wrapper"
x="{{xMove}}"
inertia
bind:touchstart="handleTouchStart"
bind:touchend="handleTouchEnd"
bind:change="handleMovableChange"
disabled="{{!canDel}}"
>
<view class="list-wrapper {{classNames}}" data-list-item="{{listItem}}">
<!-- 数据展示区域 -->
</view>
<view
class="del-btn"
style="height: {{delBtnHeight}}px"
data-list-item="{{listItem}}"
catch:tap="handleDeletePro"
>
删除
</view>
</movable-view>
</movable-area>
JS逻辑代码
data: {
xMove: 0, // 显示删除按钮的距离
delBtnHeight: 0,
startX: 0,
startY: 0, // 触摸点Y坐标
},
/**
* 设置删除按钮的高度,使其与列表项内容高度保持一致
* 该方法在组件attached生命周期时调用
*/
setDelBtnHeight() {
// 创建一个SelectorQuery对象,用于查询节点信息
const query = this.createSelectorQuery();
// 选择.list-wrapper(中间信息部分)元素并获取其边界信息(包括位置和尺寸)
query.select(".list-wrapper").boundingClientRect();
// 执行查询操作
query.exec((res) => {
// res[0]包含.list-wrapper元素的尺寸信息
if (res[0]) {
// 获取内容区域的高度
const height = res[0].height;
// 通过setData更新删除按钮的高度
// 确保删除按钮高度与列表项内容完全匹配
this.setData({
delBtnHeight: height,
});
} else {
// 如果没有找到元素,输出错误信息
console.error("Element not found");
}
});
},
handleTouchStart(e) {
this.startX = e.touches[0].pageX;
this.startY = e.touches[0].pageY;
},
// 手指抬起-触摸结束
handleTouchEnd(e) {
// 获取触摸结束时的坐标
const { pageX, pageY } = e.changedTouches[0];
const { startX, startY } = this.data;
// 计算滑动角度,防止斜着滑动误触
const angle = this.angle(
{ X: startX, Y: startY },
{ X: pageX, Y: pageY }
);
// 如果滑动角度大于30度,判定为垂直滑动,直接返回不做处理
if (Math.abs(angle) > 30) {
return;
}
// 处理水平滑动的情况
// 情况1: 向左滑动且滑动距离大于等于30px,显示删除按钮
if (
e.changedTouches[0].pageX < this.startX &&
e.changedTouches[0].pageX - this.startX <= -30
) {
this.showDeleteButton(e);
}
// 情况2: 向右滑动但滑动距离小于30px,保持删除按钮显示状态
else if (
e.changedTouches[0].pageX > this.startX &&
e.changedTouches[0].pageX - this.startX < 30
) {
this.showDeleteButton(e);
}
// 情况3: 其他情况(如向右滑动超过30px),隐藏删除按钮
else {
// 注意:点击删除按钮时也会触发这个事件
this.hideDeleteButton(e);
}
},
// 计算滑动角度的辅助函数
angle(start = {}, end = {}) {
// 计算X和Y方向的位移
const X = end.X - start.X;
const Y = end.Y - start.Y;
// 返回滑动的角度值(弧度转角度)
return (360 * Math.atan(Y / X)) / (2 * Math.PI);
},,
// 处理移动事件
handleMovableChange(e) {
if (e.detail.source === "friction") {
if (e.detail.x < -30) {
this.showDeleteButton(e);
} else {
this.hideDeleteButton(e);
}
} else if (e.detail.source === "out-of-bounds" && e.detail.x === 0) { // 用户拖动超出了可移动范围 或者 视图已经回弹到初始位置,兜底代码
this.hideDeleteButton(e);
}
},
// 隐藏删除按钮
hideDeleteButton(e) {
this.setXmove(0);
},
// 显示删除按钮
showDeleteButton(e) {
this.setXmove(-70);
},
// x轴位移
setXmove(xMove) {
this.setData({
xMove,
});
},
💡 总结
- 主要难点:
movable-view
的特性和样式调整,尤其是确保删除按钮与内容高度匹配。 - 注意点:左滑删除功能可能会与其他左滑事件(如
van-tabs
的 swipeable)发生冲突,需特别关注事件优先级和用户体验。 - 其他方案:如果使用的是原生小程序,并且使用了vant/weapp组件库,可以尝试使用
van-swipe-cell
组件
仅以此文记录工作遇到的问题