微信小程序-左滑显示删除按钮

6,949 阅读6分钟

最终效果

实现思路

  1. 使用微信小程序的movable-view组件。movable-view
  2. 使用position: absolute;,将可滑动部分(z-index值较大)放置在删除按钮(z-index值较小)之上,最开始是遮住删除按钮的。
  3. 使用touchstarttouchend属性绑定方法,控制删除按钮的显示隐藏。

代码

wxml

<view class="container">
  <movable-area>
    <movable-view direction="horizontal" out-of-bounds="{{true}}" friction="150" x="{{x}}"
      bindtouchstart="handleTouchStart" bindtouchend="handleTouchEnd" >
      <view class="card-container">
        <view>{{text}}</view>
        <view class="show-operations" catchtouchstart="toggle" catchtouchend="emptyFunc">...</view>
      </view>
    </movable-view>
  </movable-area>
  <view class="operations-content" >
    <view class="operation-button" catchtap="handleDelete">
      删除
    </view>
  </view>
</view>

在使用movable-view中用到的属性:

  • direction="horizontal",设置movable-view为横向移动。
  • out-of-bounds="{{true}}",设置超过区域之后,movable-view是否还可以移动,这个属性默认值为false,如果不添加这个属性,就不会有回弹效果。
  • friction="150"设置摩擦系数,摩擦系数越大,滑动越快停止。
  • x="{{x}}"设置x轴方向的偏移。

tip: movable-view 必须设置width和height属性,不设置默认为10px。
tip: movable-view 默认为绝对定位,top和left属性为0px

更多内容参考:movable-view

wxss

因为movable-view 默认为绝对定位,所以设置删除按钮部分的z-index值比movable-viewz-index小,就能将删除按钮遮住。

/* 可移动部分样式 */
movable-area {
  width: 510rpx; 
}
.container,
movable-view {
  box-sizing: border-box;
  width: 750rpx;
  height: 200rpx;
}
.container {
  position: relative;
  display: flex;
  flex-direction: row;
}
movable-view {
  z-index: 5; 
  padding: 10rpx;
  overflow: hidden;
  background-color: green;
}

/* 隐藏部分样式 */
.operations-content {
  position: absolute;
  display: flex;
  flex-direction: row-reverse;
  justify-content: left;
  align-items: center;
  z-index: 2; 
  right: 0;
  width: 280rpx; /* 隐藏部分的宽度 */
  height: 200rpx;
  background-color: yellow;
}
.operation-button {
  width: 150rpx;
  height: 150rpx;
  line-height: 150rpx;
  text-align: center;
  border-radius: 50%;
  margin: 0 20rpx;
  background-color: gray;
  color: #fff;
}

/* 卡片样式 */
.card-container {
  width: 100%;
  height: 180rpx;
  border-radius: 5rpx;
  font-size: 20px;
  word-break: break-all;
  background-color: rgba(255, 255, 255, 0.7);
}
.show-operations {
  position: absolute;
  bottom: 10rpx;
  right: 10rpx;
  width: 80rpx;
  height: 80rpx;
  border-radius: 50%;
  text-align: center;
  line-height: 80rpx;
}

js

通过控制movable-viewx属性来控制删除按钮的显示和隐藏。

绑定movable-viewtouchstarttouchend事件,记录下触摸开始时的x轴坐标start_x和触摸结束时的x轴坐标current_x,如果current_x - start_x小于0就说明是朝左滑的,设置movable-viewx-140来显示删除按钮,否则就是向右滑的,通过设置x值为0来隐藏删除按钮。

Component({
  properties: {
    text: {
      type: String,
      value: '示例内容示例内容'
    },
    index: Number
  },
  data: {
    x: 0,  // 注意,这里通过x属性设置的宽度的单位是px
    start_x: 0,
    operations_visible: false
  },
  methods: {
    handleTouchStart: function (event) {
      this.setData({
        start_x: event.touches[0].clientX // 触摸开始时的横坐标
      })
    },
    handleTouchEnd: function (event) {
      const current_x = event.changedTouches[0].clientX; // 触摸结束时的横坐标
      const { start_x } = this.data;
      const direction = current_x - start_x; // 判断滑动的方向

      if (direction < 0) {
        this.showOperations();
      } else {
        this.hideOperations();
      }
    },
    toggle: function () {
      let operations_visible = this.data.operations_visible;

      if (operations_visible) {
        this.hideOperations();
      } else {
        this.showOperations();
      }
    },
    handleDelete () {
      const index = this.properties.index;
      this.hideOperations();
      this.triggerEvent('delete', { index });
    },
    showOperations: function () {
      this.setData({
        x: -140,
        operations_visible: true
      });  
    },
    hideOperations: function () {
      this.setData({
        x: 0,
        operations_visible: false
      });
    },
    emptyFunc: function () {
      return false;
    }
  }
})

在显示隐藏的部分可以做一个优化,在显示状态下左滑和在隐藏状态下右滑,不用设置x的值。

    handleTouchEnd: function (event) {
      const operations_visible = this.data.operations_visible;
      const current_x = event.changedTouches[0].clientX; // 触摸结束时的横坐标
      const { start_x } = this.data;
      const direction = current_x - start_x; // 判断滑动的方向

      if (direction < 0) {
        !operations_visible && this.showOperations();
      } else {
        operations_visible && this.hideOperations();
      }
    },

json

{
  "component": true
}

解决的问题

  1. 因为有回弹的效果,所以在滑动的过程中会出现一个空隙。通过设置movable-area的宽度能够解决这个问题。
movable-area {
  width: 510rpx; 
}

这里将movable-area的宽度设置为510rpx,而不是(750-280=470)470rpx,就能让回弹的范围在黄色部分,“隐藏”这个空隙。

必须设置movable-area的宽度,否则默认宽高为10px,movable-view可滑动的范围会更大,在滑动的过程中会出现中间空隙很大的情况。

  1. 在父元素movable-view中添加了bindtouchstartbindtouchend属性用于绑定触摸开始事件和触摸结束事件。在子元素中有三个点,点击三个点的时候能够切换删除按钮的显示和隐藏。但是使用bindtap属性绑定元素的点击事件,父元素上绑定的触摸事件也会被触发。所以需要使用catch绑定事件来阻止事件冒泡。
<view class="show-operations" 
catchtouchstart="toggle" 
catchtouchend="emptyFunc">...</view>
emptyFunc: function () {
  return false;
}
  1. 实际项目使用了本文的思路,根据提出的bug做出了一些调整。

(1)滑动速度比较慢。

解决方法:调整属性,使滑动的效果变快。

<movable-view direction="horizontal" out-of-bounds="{{true}}" damping="100" friction="100" x="{{x}}"
      bind:touchstart="handleTouchStart" bind:touchend="handleTouchEnd">

(2)当上下滑动的过程中,会误触发左右滑动。

解决方法:获取滑动起始和结束时的clientY,当两者差值的绝对值大于某个范围的时候,就认为是纯上下滑动,不触发左右滑动。或者判断横向滑动的距离大于纵向来判断是左右滑动。

(3)当点击卡片时,会误触发左右滑动。

解决方法:使用1而不是0来判断左右滑

    handleTouchStart: function (event) {
      this.hideAllOperations();
      const { clientX, clientY } = event.touches[0];
      this.setData({
        start_x: clientX,
        start_y: clientY
      });
    },
    handleTouchEnd: function (event) {
      const { clientX, clientY } = event.changedTouches[0];
      const { start_x, start_y } = this.data;

      if (Math.abs(clientY - start_y) > 50)  return; // 处理上下滑动误触左右滑动的情况
      const direction = clientX - start_x;

      // 这里使用1来判断方向,保证用户在非滑动时不触发滚动(有时点击也会产生些许x轴坐标的变化)
      if (direction < -1) {
        this.showOperations();
      } else if (direction > 1) {
        this.hideOperations();
      } else {
        this.toBrandDetail();
      }
    },

(4)在列表中只能有一个隐藏的按钮是显示出来的,但是当前的方式没有对滑动卡片的数量做限制。

解决方法:在父组件中定义一个数组(数组元素为滑动卡片的x值),用于控制所有卡片的显示隐藏。当滑动开始时将数组中的所有值设置为0,当左滑显示卡片的时候,将滑动显示出按钮的那个卡片对应的数组中的x值设置为-85

更多微信小程序事件相关,参考:微信小程序事件

使用列表

如果要以列表的形式使用,就在父组件中引入该组件,并通过数组来控制列表。

父组件的wxml:

<view>
  <block wx:for="{{test_list}}" wx:key="{{index}}">
    <movable-component text="{{item}}" index="{{index}}" class="list-item" catch:delete="deleteItem"/>
  </block>
</view>

父组件的js:

Page({ 
  data: {
    test_list: null
  },
  onLoad: function () {
    let list_arr = [];
    for (let i = 0; i < 5; i++) {
      list_arr.push(`${Array(10).fill(i + 1).join(' ')}`);
    }
    this.setData({
      test_list: list_arr
    })
  },
  deleteItem: function (event) {
    // 一些其余操作,比如发起删除请求
    const index = event.detail.index;
    let arr = this.data.test_list;
    arr.splice(index, 1);
    this.setData({
      test_list: arr
    })
  }
})

父组件的wxss:

.list-item {
  display: block;
  margin: 20rpx 0;
}

父组件的json:

{
  "usingComponents": {
    "movable-component": "./components/movableView/movableView"
  }
}

PS: 如发现文中可优化或不当之处,请不吝赐教(。・`ω´・)。