实现左滑删除功能:原生小程序的探索与实践

103 阅读4分钟

📱 背景

产品要求实现左滑删除的功能,动画是挺丝滑的,但是我觉得牺牲了一定理解性,用户找个删除可能找半天,不过仁者见仁智者见智。

技术栈

🌟 场景1: 固定高度列表项

处理思路

将最右边的删除按钮隐藏(位置在可视区域外),只有通过滑动才能使其进入视野。 固定高度.gif

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,以避免被纵向或横向挤压。

image.png

image.png

image.png

  • 最大拖动距离:横向可以拖动的最大距离取决于 movable-view 的宽度。 image.png

🎯 场景2:动态高度列表项

最右侧删除按钮相对于 movable-view 做绝对定位,同时监听其高度,对高度进行动态同步。通过监听 touch 事件判断用户是误触还是左右滑动,并通过 change 事件处理移动事件的结果,对不同滑动距离做不同操作。

动态高度.gif

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>

image.png

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组件

仅以此文记录工作遇到的问题