封面圖是 ios 常見的設計滑動出現刪除按鈕(swipe to delete)
今天來分析怎麼做出來
A1: 監聽事件 (touchstart
, touchmove
, touchend
); 記住touch當下的x & 離開的x,兩者求移動距離
A2: 利用css的translateX
以下請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
讀者可以試試看最後做出來的效果
<!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>
this.startX = event.touches[0].clientX;
//記錄當下的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;
}
const diffX = this.startX - this.currentX;
//當結束時移動距離大於40則判斷為觸發,觸發會往左移動80px
const finishOffset = diffX > 40 ? -80 : 0;
this.items[index].offset = finishOffset;
this.isSwiping = false;