iT邦幫忙

2024 iThome 鐵人賽

DAY 14
0
JavaScript

PM說: RD大大,這個功能要怎麼寫啊?系列 第 14

PM 說: 要怎麼用網頁實現`ios滑動出現刪除按鈕`的效果?

  • 分享至 

  • xImage
  •  

前言

封面圖是 ios 常見的設計滑動出現刪除按鈕(swipe to delete)
今天來分析怎麼做出來

拆解問題

  1. 怎麼判斷滑動(要知道方向)
  2. 滑動時要修改卡片UI

A1: 監聽事件 (touchstart, touchmove, touchend); 記住touch當下的x & 離開的x,兩者求移動距離
A2: 利用css的translateX


Touch 事件

以下請chatGPT解釋一下各參數

在 touch 事件中,以下是每個屬性的解釋:

clientX: 觸控點在視口(viewport)中的 X 軸位置,單位為像素,從瀏覽器內容區域的左上角(不包括邊框或滾動條)開始計算。

clientY: 觸控點在視口中的 Y 軸位置,單位為像素,從瀏覽器內容區域的左上角開始計算。

force: 用於表示觸控的壓力大小,範圍通常為 0 到 1,1 表示全力壓下,0 表示沒有壓力。

identifier: 每個觸控點的唯一標識符,用來區分多點觸控中的不同觸控點。

pageX: 觸控點相對於整個頁面的 X 軸位置,包含頁面的滾動距離。這是指相對於頁面左上角(包括滾動)的位置。

pageY: 觸控點相對於整個頁面的 Y 軸位置,包含頁面的滾動距離。

radiusX: 觸控點的橢圓邊界在 X 軸方向的半徑,通常用來描述觸控的區域大小。

radiusY: 觸控點的橢圓邊界在 Y 軸方向的半徑。

rotationAngle: 觸控點的旋轉角度,範圍為 0 到 360 度,用來描述觸控點橢圓的旋轉。

screenX: 觸控點在整個螢幕上的 X 軸位置,從螢幕左上角(包含瀏覽器的邊框和滾動條)開始計算。

screenY: 觸控點在整個螢幕上的 Y 軸位置,從螢幕左上角開始計算。

這些數據通常在處理多點觸控(例如手勢識別、滑動事件)時非常有用,可以幫助開發者更精確地理解觸控事件的行為。

成果

這邊因為要縮減操作DOM的程式碼,使用了 cdn-vue

讀者可以試試看最後做出來的效果

demo

程式碼

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>iOS-style Swipe-to-Delete Demo</title>
    <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
    <style>
      body {
        margin: 0;
        padding: 20px;
        background-color: #f0f0f0;
      }
      .list-item {
        position: relative;
        background-color: #fff;
        padding: 20px;
        margin-bottom: 10px;
        border-radius: 10px;
        box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
        overflow: hidden;
        touch-action: pan-y;
      }
      .list-item-content {
        transition: transform 0.3s ease;
      }
      .delete-btn {
        position: absolute;
        right: 0;
        top: 0;
        bottom: 0;
        width: 80px;
        background-color: #ff3b30;
        color: white;
        display: flex;
        align-items: center;
        justify-content: center;
        transform: translateX(100%);
        transition: transform 0.3s ease;
      }
    </style>
  </head>
  <body>
    <div id="app">
      <h3>請使用 手機模式測試!</h3>
      <div
        v-for="(item, index) in items"
        :key="index"
        class="list-item"
        @touchstart="touchStart($event, index)"
        @touchmove="touchMove($event, index)"
        @touchend="touchEnd($event, index)"
      >
        <div
          class="list-item-content"
          :style="{ transform: `translateX(${item.offset}px)` }"
        >
          {{ item.text }}{{item.offset}}
        </div>
        <div
          class="delete-btn"
          :style="{ transform: `translateX(${80 + item.offset}px)` }"
          @click="deleteItem(item, index)"
        >
          Delete
        </div>
      </div>
    </div>

    <script>
      const app = Vue.createApp({
        data() {
          return {
            items: [
              { text: "Item 1,offset:", offset: 0 },
              { text: "Item 2,offset:", offset: 0 },
              { text: "Item 3,offset:", offset: 0 },
              { text: "Item 4,offset:", offset: 0 },
              { text: "Item 5,offset:", offset: 0 },
            ],
            startX: 0,
            currentX: 0,
            isSwiping: false,
          };
        },
        methods: {
          touchStart(event, index) {
            this.startX = event.touches[0].clientX;
            this.isSwiping = false;
          },
          touchMove(event, index) {
            this.currentX = event.touches[0].clientX;
            const diffX = this.startX - this.currentX;
            // 加上距離5來區分 tap and swipe
            if (diffX > 5 || diffX < -5) {
              this.isSwiping = true;
              // Prevent scrolling
              event.preventDefault();
            }

            if (diffX > 0 && diffX <= 80) {
              this.items[index].offset = -diffX;
              return;
            }
          },
          touchEnd(event, index) {
            if (!this.isSwiping) {
              this.isSwiping = false;
              return;
            }

            const diffX = this.startX - this.currentX;
            //當結束時移動距離大於40則觸發
            const finishOffset = diffX > 40 ? -80 : 0;
            this.items[index].offset = finishOffset;
            this.isSwiping = false;
          },
          deleteItem(item, index) {
            const hint = confirm(`是否刪除${item.text}?`);
            if (hint) {
              this.items.splice(index, 1);
            } else {
              // Reset
              this.items[index].offset = 0;
            }
          },
        },
      });

      app.mount("#app");
    </script>
  </body>
</html>

重點

  1. touchStart(開始點擊)
    記錄起始的x
this.startX = event.touches[0].clientX;
  1. touchMove(移動中)
//記錄當下的x
this.currentX = event.touches[0].clientX;
//算出移動距離
const diffX = this.startX - this.currentX;

//判斷移動距離是否符合移動條件
  if (diffX > 5 || diffX < -5) {
    this.isSwiping = true;
    // Prevent scrolling
    event.preventDefault();
  }
//在範圍內則修改UI
  if (diffX > 0 && diffX <= 80) {
    this.items[index].offset = -diffX;
    return;
  }
  1. touchEnd(點擊結束)
  const diffX = this.startX - this.currentX;
  //當結束時移動距離大於40則判斷為觸發,觸發會往左移動80px
  const finishOffset = diffX > 40 ? -80 : 0;
  this.items[index].offset = finishOffset;
  this.isSwiping = false;

上一篇
PM 說: 要怎麼在手機上監聽前端 console 以及 API 資訊?
下一篇
PM 說: 要怎麼監聽網頁切換到背景?
系列文
PM說: RD大大,這個功能要怎麼寫啊?30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言