You've already forked SmartisanNote.Remake
添加了便签条滑动动画
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="code-fun-flex-row code-fun-justify-center code-fun-relative list-item_7" @click="handlePress">
|
<div class="code-fun-flex-row code-fun-justify-center code-fun-relative list-item_7">
|
||||||
<div class="code-fun-flex-col code-fun-relative section_17">
|
<!-- 便签条 -->
|
||||||
|
<div class="code-fun-flex-col code-fun-relative section_17" @click="handlePress" @touchstart="handleTouchStart" @touchmove="handleTouchMove" @touchend="handleTouchEnd" :style="{ transform: `translateX(${slideOffset}px)` }">
|
||||||
<div class="code-fun-flex-row code-fun-justify-between">
|
<div class="code-fun-flex-row code-fun-justify-between">
|
||||||
<!-- 便签编辑时间 -->
|
<!-- 便签编辑时间 -->
|
||||||
<span class="font_2 text_18">{{ formattedDate }}</span>
|
<span class="font_2 text_18">{{ formattedDate }}</span>
|
||||||
@@ -22,13 +23,13 @@
|
|||||||
<button class="btn_delete">
|
<button class="btn_delete">
|
||||||
<span>删除</span>
|
<span>删除</span>
|
||||||
</button>
|
</button>
|
||||||
<!-- 便签夹 -->
|
<!-- 便签夹未滑动状态 -->
|
||||||
<img class="image_27 pos_18" src="/assets/icons/drawable-xxhdpi/note_item_clip_normal.png" />
|
<img class="image_27 pos_18" :src="isSliding ? '/assets/icons/drawable-xxhdpi/note_item_clip_up.png' : '/assets/icons/drawable-xxhdpi/note_item_clip_normal.png'" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { computed } from 'vue'
|
import { computed, ref } from 'vue'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
title: {
|
title: {
|
||||||
@@ -67,15 +68,29 @@ const props = defineProps({
|
|||||||
type: Function,
|
type: Function,
|
||||||
default: () => {},
|
default: () => {},
|
||||||
},
|
},
|
||||||
|
onDelete: {
|
||||||
|
type: Function,
|
||||||
|
default: () => {},
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 滑动相关状态
|
||||||
|
const slideOffset = ref(0)
|
||||||
|
const startX = ref(0)
|
||||||
|
const isSliding = ref(false)
|
||||||
|
const isSlided = ref(false) // 是否已经滑动到阈值
|
||||||
|
|
||||||
const formattedDate = computed(() => {
|
const formattedDate = computed(() => {
|
||||||
// 简单的日期格式化,实际项目中可能需要更复杂的处理
|
// 简单的日期格式化,实际项目中可能需要更复杂的处理
|
||||||
return props.date
|
return props.date
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 滑动阈值(删除按钮宽度)
|
||||||
|
const SLIDE_THRESHOLD = 64 // 4rem 转换为 px
|
||||||
|
|
||||||
const handlePress = () => {
|
const handlePress = () => {
|
||||||
if (props.onPress) {
|
// 只有在未滑动状态下才触发点击事件
|
||||||
|
if (slideOffset.value === 0 && props.onPress) {
|
||||||
props.onPress()
|
props.onPress()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -91,6 +106,75 @@ const handleTopToggle = () => {
|
|||||||
props.onTopToggle()
|
props.onTopToggle()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleDelete = () => {
|
||||||
|
if (props.onDelete) {
|
||||||
|
props.onDelete()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 触摸开始
|
||||||
|
const handleTouchStart = e => {
|
||||||
|
// 重置滑动状态
|
||||||
|
startX.value = e.touches[0].clientX
|
||||||
|
}
|
||||||
|
|
||||||
|
// 触摸移动
|
||||||
|
const handleTouchMove = e => {
|
||||||
|
if (!startX.value) return
|
||||||
|
|
||||||
|
const currentX = e.touches[0].clientX
|
||||||
|
const diffX = currentX - startX.value
|
||||||
|
|
||||||
|
// 只处理右滑动(正值)
|
||||||
|
if (diffX > 0) {
|
||||||
|
e.preventDefault() // 防止页面滚动
|
||||||
|
|
||||||
|
// 设置滑动状态
|
||||||
|
isSliding.value = true
|
||||||
|
|
||||||
|
// 应用阻尼效果
|
||||||
|
let offset = 0
|
||||||
|
if (diffX <= SLIDE_THRESHOLD) {
|
||||||
|
// 线性滑动
|
||||||
|
offset = diffX
|
||||||
|
} else {
|
||||||
|
// 超过阈值后应用阻尼效果
|
||||||
|
const excess = diffX - SLIDE_THRESHOLD
|
||||||
|
offset = SLIDE_THRESHOLD + excess * 0.03 // 0.3 为阻尼系数
|
||||||
|
}
|
||||||
|
|
||||||
|
slideOffset.value = offset
|
||||||
|
isSlided.value = offset >= SLIDE_THRESHOLD
|
||||||
|
} else if (diffX < 0) {
|
||||||
|
// 左滑动,将便签条移回原位
|
||||||
|
const offset = Math.max(0, slideOffset.value + diffX)
|
||||||
|
slideOffset.value = offset
|
||||||
|
isSlided.value = offset >= SLIDE_THRESHOLD
|
||||||
|
|
||||||
|
// 更新 startX 以确保连续滑动的正确性
|
||||||
|
startX.value = currentX
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 触摸结束
|
||||||
|
const handleTouchEnd = () => {
|
||||||
|
if (!startX.value) return
|
||||||
|
|
||||||
|
// 如果滑动超过阈值,保持滑出状态;否则回弹
|
||||||
|
if (slideOffset.value >= SLIDE_THRESHOLD) {
|
||||||
|
slideOffset.value = SLIDE_THRESHOLD
|
||||||
|
isSlided.value = true
|
||||||
|
} else {
|
||||||
|
// 回弹到初始位置
|
||||||
|
slideOffset.value = 0
|
||||||
|
isSliding.value = false
|
||||||
|
isSlided.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重置起始位置
|
||||||
|
startX.value = 0
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
@@ -107,13 +191,15 @@ const handleTopToggle = () => {
|
|||||||
height: 2rem;
|
height: 2rem;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
left: 2rem;
|
left: 1rem;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
transform: translate(0, -50%);
|
transform: translate(0, -50%);
|
||||||
color: white;
|
color: white;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
|
border: none;
|
||||||
|
padding: 0;
|
||||||
span {
|
span {
|
||||||
margin-right: 0.2rem;
|
margin-right: 0.7rem;
|
||||||
font-size: 0.6rem;
|
font-size: 0.6rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -128,6 +214,7 @@ const handleTopToggle = () => {
|
|||||||
z-index: 2;
|
z-index: 2;
|
||||||
padding: 0.44rem 0.69rem 0.88rem 2.69rem;
|
padding: 0.44rem 0.69rem 0.88rem 2.69rem;
|
||||||
box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.23);
|
box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.23);
|
||||||
|
transition: transform 0.3s ease-out;
|
||||||
.font_2 {
|
.font_2 {
|
||||||
font-size: 0.71rem;
|
font-size: 0.71rem;
|
||||||
line-height: 0.71rem;
|
line-height: 0.71rem;
|
||||||
@@ -181,6 +268,7 @@ const handleTopToggle = () => {
|
|||||||
top: 50%;
|
top: 50%;
|
||||||
transform: translate(0, -50%);
|
transform: translate(0, -50%);
|
||||||
z-index: 3;
|
z-index: 3;
|
||||||
|
transition: transform 0.3s ease-out;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -50,7 +50,9 @@
|
|||||||
:hasImage="note.hasImage || false"
|
:hasImage="note.hasImage || false"
|
||||||
:onPress="() => handleNotePress(note.id)"
|
:onPress="() => handleNotePress(note.id)"
|
||||||
:onStarToggle="() => handleStarToggle(note.id)"
|
:onStarToggle="() => handleStarToggle(note.id)"
|
||||||
:onTopToggle="() => handleTopToggle(note.id)" />
|
:onTopToggle="() => handleTopToggle(note.id)"
|
||||||
|
:onDelete="() => handleDeleteNote(note.id)"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user