The final result

Implementation approach

  1. Using wechat appletmovable-viewComponents.movable-view.
  2. useposition: absolute;, the sliding part (z-indexLarge value) placed on the Delete button (z-indexSmall), the delete button is initially obscured.
  3. usetouchstart,touchendProperty binding method to control the display and hide of the delete button.

code

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">delete</view>
  </view>
</view>
Copy the code

Properties to use with movable-View:

  • direction="horizontal"To set upmovable-viewIs a lateral move.
  • out-of-bounds="{{true}}", after setting beyond the region,movable-viewThe default value of this property isfalseIf you do not add this property, there will be no rebound effect.
  • friction="150"Set the friction coefficient. The greater the friction coefficient, the faster the slide will stop.
  • x="{{x}}"Sets the offset in the X-axis direction.

Tip: movable-View must set width and height properties, not default to 10px. Tip: movable-view defaults to absolute positioning, with top and left set to 0px

For more information: Movable-View.

wxss

Because the default positioning of movable-View is absolute, setting the z-index of the delete button part smaller than the Z-index of movable-View would block the delete button.

/* Removable part style */
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;
}

/* Hide some styles */
.operations-content {
  position: absolute;
  display: flex;
  flex-direction: row-reverse;
  justify-content: left;
  align-items: center;
  z-index: 2; 
  right: 0;
  width: 280rpx; /* The width of the hidden part */
  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 style */
.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;
}
Copy the code

js

Control the display and hiding of the delete button by controlling the X property of movable-View.

Bind the touchstart and touchend events of the movable-View to record start_x at the start of the touch and current_x at the end of the touch. If current_x-start_x is less than 0, the current_x-start_x is moving left. Set the x of movable-View to -140 to show the delete button, or slide right to hide the delete button by setting the x value to 0.

Component({
  properties: {
    text: {
      type: String.value: 'Example Content Example content'
    },
    index: Number
  },
  data: {
    x: 0.// Note that the unit of the width set by the x property is px
    start_x: 0.operations_visible: false
  },
  methods: {
    handleTouchStart: function (event) {
      this.setData({
        start_x: event.touches[0].clientX // The abscissa at the beginning of the touch})},handleTouchEnd: function (event) {
      const current_x = event.changedTouches[0].clientX; // The x-coordinate at the end of the touch
      const { start_x } = this.data;
      const direction = current_x - start_x; // Determine the direction of the slide

      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; }}})Copy the code

An optimization can be made for the hidden part of the display. You can swipe left in the display state and right in the hidden state without setting the value of x.

    handleTouchEnd: function (event) {
      const operations_visible = this.data.operations_visible;
      const current_x = event.changedTouches[0].clientX; // The x-coordinate at the end of the touch
      const { start_x } = this.data;
      const direction = current_x - start_x; // Determine the direction of the slide

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

json

{
  "component": true
}
Copy the code

Problem solved

  1. Because of the rebound effect, there will be a gap in the process of sliding. By setting themovable-areaThe width of is able to solve this problem.
movable-area {
  width: 510rpx; 
}
Copy the code

Setting the width of the movable-area to 510rpx instead of 470rpx (750-280=470) enabled the bouncing-back range to be in yellow, “hiding” the void.

You have to set the width of the movable-Area, otherwise the default width and height of the movable-View will be 10px, and the movable view will slide wider and have a lot of space between them to slide.

  1. The parent elementmovable-viewAdd thebindtouchstartandbindtouchendProperty is used to bind touch start and touch end events. There are three dots in the child element, and you can toggle the delete button between show and hide when you click the three dots. But the use ofbindtapThe click event on the property bound element and the touch event on the parent element bound are also triggered. So you need to usecatchBind events to prevent event bubbling.
<view class="show-operations" 
catchtouchstart="toggle" 
catchtouchend="emptyFunc">.</view>
Copy the code
emptyFunc: function () {
  return false;
}
Copy the code
  1. The ideas in this article were used in a real project, with some adjustments based on the proposed bugs.

(1) The sliding speed is slow.

Solution: Adjust the properties to make the effect of sliding faster.

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

(2) When sliding up and down, left and right sliding will be triggered by mistake.

Solution: Get the clientY at the start and end of the slide, and when the absolute value of the difference between the two is greater than a certain range, it is considered a pure up and down slide, not a left and right slide. Or the distance of the lateral slide is greater than that of the longitudinal slide.

(3) When clicking the card, it will trigger left-right sliding by mistake.

Solution: Use a 1 instead of a 0 for left-right slip

    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; // Handle the case where the up and down slide mistakenly touches the left and right slide
      const direction = clientX - start_x;

      // Use 1 here to determine the direction of the user, to ensure that the user does not scroll when not sliding (sometimes clicking on the x axis changes a little bit)
      if (direction < - 1) {
        this.showOperations();
      } else if (direction > 1) {
        this.hideOperations();
      } else {
        this.toBrandDetail(); }},Copy the code

(4) Only one hidden button can be displayed in the list, but there is no limit to the number of sliding cards in the current method.

Solution: Define an array in the parent component (the elements of the array are the x values of the sliding cards) to control the display and hide of all the cards. Set all the values in the array to 0 when the slide starts, and set the x value in the array for the card that the slide shows the button to -85 when the left slide shows the card.

For more information about wechat applet events, see wechat Applet events.

Use the list

If you want to use it as a list, introduce the component in the parent component and control the list through an array.

WXML for parent component:

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

Parent js:

Page({ 
  data: {
    test_list: null
  },
  onLoad: function () {
    let list_arr = [];
    for (let i = 0; i < 5; i++) {
      list_arr.push(`The ${Array(10).fill(i + 1).join(' ')}`);
    }
    this.setData({
      test_list: list_arr
    })
  },
  deleteItem: function (event) {
    // Some other operations, such as making a delete request
    const index = event.detail.index;
    let arr = this.data.test_list;
    arr.splice(index, 1);
    this.setData({
      test_list: arr
    })
  }
})
Copy the code

WXSS for parent component:

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

Json for parent component:

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

PS: If found in the article can be optimized or inappropriate, please kindly comment (. · ω´ ·).