polish global drag state with minimal futuristic feedback

This commit is contained in:
jingrow 2025-11-19 17:09:59 +08:00
parent 4d4ab272ca
commit 3a31d9db69

View File

@ -1,5 +1,16 @@
<template>
<div class="remove-background-page">
<div
class="remove-background-page"
@dragenter.prevent="handleDragEnter"
@dragover.prevent="handleDragOver"
@dragleave="handleDragLeave"
@drop.prevent="handleDrop"
>
<div v-if="isDragging" class="global-drag-overlay">
<div class="overlay-content">
<p>{{ t('Drop image anywhere to remove background') }}</p>
</div>
</div>
<!-- 文件输入框始终存在 -->
<input
ref="fileInputRef"
@ -39,9 +50,6 @@
v-if="!uploadedImage"
class="upload-area"
:class="{ 'dragging': isDragging }"
@drop.prevent="handleDrop"
@dragover.prevent="handleDragOver"
@dragleave="handleDragLeave"
@click="triggerFileInput"
>
<div class="upload-content">
@ -191,6 +199,7 @@ const currentHistoryIndex = ref<number>(-1) // -1 表示当前正在处理的图
//
const isDragging = ref(false)
const dragCounter = ref(0)
const processing = ref(false)
const splitPosition = ref(0) // 线0
const comparisonContainerRef = ref<HTMLElement | null>(null)
@ -214,16 +223,38 @@ const handleFileSelect = (event: Event) => {
}
}
//
const hasFiles = (event: DragEvent) => {
const types = event.dataTransfer?.types
if (!types) return false
return Array.from(types).includes('Files')
}
//
const handleDragOver = () => {
const handleDragEnter = (event: DragEvent) => {
if (!hasFiles(event)) return
dragCounter.value += 1
isDragging.value = true
}
const handleDragLeave = () => {
isDragging.value = false
const handleDragOver = (event: DragEvent) => {
if (!hasFiles(event)) return
event.dataTransfer!.dropEffect = 'copy'
isDragging.value = true
}
const handleDragLeave = (event: DragEvent) => {
if (!hasFiles(event)) return
dragCounter.value = Math.max(0, dragCounter.value - 1)
if (dragCounter.value === 0) {
isDragging.value = false
}
}
const handleDrop = (event: DragEvent) => {
if (!hasFiles(event)) return
event.preventDefault()
dragCounter.value = 0
isDragging.value = false
const file = event.dataTransfer?.files[0]
if (file && file.type.startsWith('image/')) {
@ -497,6 +528,112 @@ const handleDownload = () => {
min-height: 100vh;
display: flex;
flex-direction: column;
position: relative;
}
.global-drag-overlay {
position: fixed;
inset: 0;
background: radial-gradient(circle at center, rgba(248, 250, 252, 0.92), rgba(248, 250, 252, 0.82));
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
pointer-events: none;
animation: fade-in 0.15s ease;
&::before {
content: '';
position: absolute;
width: 55vmax;
height: 55vmax;
border-radius: 35%;
border: 1px solid rgba(148, 163, 184, 0.25);
animation: rotate 12s linear infinite;
pointer-events: none;
}
}
.overlay-content {
position: relative;
display: inline-flex;
align-items: center;
gap: 8px;
padding: 18px 30px;
color: #0f172a;
font-size: 18px;
font-weight: 600;
text-align: center;
letter-spacing: 0.04em;
text-transform: uppercase;
&::before {
content: '';
position: absolute;
inset: -10px;
border-radius: 999px;
border: 1px solid rgba(79, 70, 229, 0.35);
opacity: 0.7;
animation: pulse-line 1.8s ease-out infinite;
}
&::after {
content: '';
position: absolute;
inset: -28px 16px;
border-radius: 999px;
background: linear-gradient(90deg, rgba(59, 130, 246, 0), rgba(59, 130, 246, 0.24), rgba(59, 130, 246, 0));
filter: blur(10px);
opacity: 0.65;
animation: shimmer 2.4s linear infinite;
}
}
@keyframes fade-in {
from {
opacity: 0;
transform: scale(0.98);
}
to {
opacity: 1;
transform: scale(1);
}
}
@keyframes rotate {
to {
transform: rotate(360deg);
}
}
@keyframes pulse-line {
0% {
transform: scale(0.9);
opacity: 0;
}
50% {
transform: scale(1);
opacity: 0.5;
}
100% {
transform: scale(1.05);
opacity: 0;
}
}
@keyframes shimmer {
0% {
opacity: 0.2;
}
50% {
opacity: 0.7;
}
100% {
opacity: 0.2;
}
}
.remove-background-page > .page-content {