添加了便签条滑动动画

This commit is contained in:
User
2025-10-10 17:15:10 +08:00
parent a03384f170
commit 7ee1a27917
2 changed files with 108 additions and 18 deletions

View File

@@ -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>

View File

@@ -42,15 +42,17 @@
<div style="flex: 1"> <div style="flex: 1">
<div v-for="note in filteredAndSortedNotes" :key="note.id" style="margin: 0.4rem 0"> <div v-for="note in filteredAndSortedNotes" :key="note.id" style="margin: 0.4rem 0">
<NoteItem <NoteItem
:title="note.title" :title="note.title"
:content="note.content" :content="note.content"
:date="formatDate(note.updatedAt)" :date="formatDate(note.updatedAt)"
:isStarred="note.isStarred" :isStarred="note.isStarred"
:isTop="note.isTop || false" :isTop="note.isTop || false"
: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>