improve RemoveBackground comparison view and split line styling
This commit is contained in:
parent
6d861a2829
commit
cac901e235
@ -39,69 +39,67 @@
|
|||||||
<div class="image-preview-container">
|
<div class="image-preview-container">
|
||||||
<div class="preview-header">
|
<div class="preview-header">
|
||||||
<h3>{{ t('Image Preview') }}</h3>
|
<h3>{{ t('Image Preview') }}</h3>
|
||||||
<button class="change-image-btn" @click="resetUpload">
|
<div class="header-actions">
|
||||||
<i class="fa fa-refresh"></i>
|
<n-button
|
||||||
{{ t('Change Image') }}
|
v-if="resultImage"
|
||||||
</button>
|
size="medium"
|
||||||
|
@click="handleDownload"
|
||||||
|
class="download-btn"
|
||||||
|
>
|
||||||
|
<template #icon>
|
||||||
|
<i class="fa fa-download"></i>
|
||||||
|
</template>
|
||||||
|
{{ t('Download') }}
|
||||||
|
</n-button>
|
||||||
|
<button class="change-image-btn" @click="resetUpload">
|
||||||
|
<i class="fa fa-refresh"></i>
|
||||||
|
{{ t('Change Image') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="preview-grid">
|
<!-- 单窗口对比视图 -->
|
||||||
<!-- 原图 -->
|
<div class="comparison-view" v-if="resultImage">
|
||||||
<div class="preview-card">
|
<div class="comparison-container" ref="comparisonContainerRef">
|
||||||
<div class="preview-label">{{ t('Original') }}</div>
|
<!-- 原图(显示左侧部分) -->
|
||||||
<div class="image-wrapper">
|
<div
|
||||||
|
class="comparison-image original-image"
|
||||||
|
:style="{ clipPath: `inset(0 ${100 - splitPosition}% 0 0)` }"
|
||||||
|
>
|
||||||
<img :src="uploadedImageUrl" alt="Original" />
|
<img :src="uploadedImageUrl" alt="Original" />
|
||||||
<div v-if="processing" class="processing-overlay">
|
|
||||||
<div class="spinner"></div>
|
|
||||||
<p>{{ t('Processing...') }}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
<!-- 去背景后的图片(显示右侧部分) -->
|
||||||
<!-- 去背景后的图片 -->
|
<div
|
||||||
<div class="preview-card">
|
class="comparison-image result-image"
|
||||||
<div class="preview-label">{{ t('Background Removed') }}</div>
|
:style="{ clipPath: `inset(0 0 0 ${splitPosition}%)` }"
|
||||||
<div class="image-wrapper">
|
>
|
||||||
<div v-if="!resultImage && !processing" class="placeholder">
|
<img :src="resultImageUrl" alt="Result" />
|
||||||
<i class="fa fa-image"></i>
|
</div>
|
||||||
<p>{{ t('Result will appear here') }}</p>
|
|
||||||
</div>
|
<!-- 拖动竖线 -->
|
||||||
<img v-else-if="resultImage" :src="resultImageUrl" alt="Result" />
|
<div
|
||||||
<div v-else class="processing-overlay">
|
class="split-line"
|
||||||
<div class="spinner"></div>
|
:class="{ 'dragging': isDraggingSplitLine }"
|
||||||
<p>{{ t('Processing...') }}</p>
|
:style="{ left: `${splitPosition}%` }"
|
||||||
|
@mousedown.prevent.stop="handleSplitLineMouseDown"
|
||||||
|
>
|
||||||
|
<div class="split-line-handle">
|
||||||
|
<i class="fa fa-arrows-h"></i>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 操作按钮 -->
|
<!-- 处理中或未处理状态 -->
|
||||||
<div class="action-buttons">
|
<div v-else class="single-image-view">
|
||||||
<n-button
|
<div class="image-wrapper">
|
||||||
type="primary"
|
<img :src="uploadedImageUrl" alt="Original" />
|
||||||
size="large"
|
<div v-if="processing" class="processing-overlay">
|
||||||
:loading="processing"
|
<div class="spinner"></div>
|
||||||
:disabled="!uploadedImage || processing"
|
<p>{{ t('Processing...') }}</p>
|
||||||
@click="handleRemoveBackground"
|
</div>
|
||||||
class="process-btn"
|
</div>
|
||||||
>
|
|
||||||
<template #icon>
|
|
||||||
<i class="fa fa-magic"></i>
|
|
||||||
</template>
|
|
||||||
{{ processing ? t('Processing...') : t('Remove Background') }}
|
|
||||||
</n-button>
|
|
||||||
|
|
||||||
<n-button
|
|
||||||
v-if="resultImage"
|
|
||||||
size="large"
|
|
||||||
@click="handleDownload"
|
|
||||||
class="download-btn"
|
|
||||||
>
|
|
||||||
<template #icon>
|
|
||||||
<i class="fa fa-download"></i>
|
|
||||||
</template>
|
|
||||||
{{ t('Download') }}
|
|
||||||
</n-button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -157,6 +155,9 @@ const resultImageUrl = computed(() => {
|
|||||||
// 状态
|
// 状态
|
||||||
const isDragging = ref(false)
|
const isDragging = ref(false)
|
||||||
const processing = ref(false)
|
const processing = ref(false)
|
||||||
|
const splitPosition = ref(50) // 分割线位置(百分比)
|
||||||
|
const comparisonContainerRef = ref<HTMLElement | null>(null)
|
||||||
|
const isDraggingSplitLine = ref(false)
|
||||||
|
|
||||||
// 触发文件选择
|
// 触发文件选择
|
||||||
const triggerFileInput = () => {
|
const triggerFileInput = () => {
|
||||||
@ -209,11 +210,14 @@ const processFile = (file: File) => {
|
|||||||
|
|
||||||
uploadedImage.value = file
|
uploadedImage.value = file
|
||||||
resultImage.value = ''
|
resultImage.value = ''
|
||||||
|
splitPosition.value = 50 // 重置分割线位置
|
||||||
|
|
||||||
// 创建预览URL
|
// 创建预览URL
|
||||||
const reader = new FileReader()
|
const reader = new FileReader()
|
||||||
reader.onload = (e) => {
|
reader.onload = (e) => {
|
||||||
uploadedImageUrl.value = e.target?.result as string
|
uploadedImageUrl.value = e.target?.result as string
|
||||||
|
// 上传后自动触发去背景
|
||||||
|
handleRemoveBackground()
|
||||||
}
|
}
|
||||||
reader.readAsDataURL(file)
|
reader.readAsDataURL(file)
|
||||||
}
|
}
|
||||||
@ -223,11 +227,36 @@ const resetUpload = () => {
|
|||||||
uploadedImage.value = null
|
uploadedImage.value = null
|
||||||
uploadedImageUrl.value = ''
|
uploadedImageUrl.value = ''
|
||||||
resultImage.value = ''
|
resultImage.value = ''
|
||||||
|
splitPosition.value = 50
|
||||||
if (fileInputRef.value) {
|
if (fileInputRef.value) {
|
||||||
fileInputRef.value.value = ''
|
fileInputRef.value.value = ''
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 拖动竖线处理
|
||||||
|
const handleSplitLineMouseDown = (e: MouseEvent) => {
|
||||||
|
e.preventDefault()
|
||||||
|
isDraggingSplitLine.value = true
|
||||||
|
|
||||||
|
const handleMouseMove = (moveEvent: MouseEvent) => {
|
||||||
|
if (!comparisonContainerRef.value || !isDraggingSplitLine.value) return
|
||||||
|
|
||||||
|
const rect = comparisonContainerRef.value.getBoundingClientRect()
|
||||||
|
const x = moveEvent.clientX - rect.left
|
||||||
|
const percentage = Math.max(0, Math.min(100, (x / rect.width) * 100))
|
||||||
|
splitPosition.value = percentage
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleMouseUp = () => {
|
||||||
|
isDraggingSplitLine.value = false
|
||||||
|
document.removeEventListener('mousemove', handleMouseMove)
|
||||||
|
document.removeEventListener('mouseup', handleMouseUp)
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('mousemove', handleMouseMove)
|
||||||
|
document.addEventListener('mouseup', handleMouseUp)
|
||||||
|
}
|
||||||
|
|
||||||
// 处理去背景
|
// 处理去背景
|
||||||
const handleRemoveBackground = async () => {
|
const handleRemoveBackground = async () => {
|
||||||
// 检查用户是否已登录
|
// 检查用户是否已登录
|
||||||
@ -378,6 +407,14 @@ const handleDownload = () => {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.remove-background-page > .page-content {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 1000px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.page-header {
|
.page-header {
|
||||||
@ -592,9 +629,114 @@ h1 {
|
|||||||
letter-spacing: 0.5px;
|
letter-spacing: 0.5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 对比视图 */
|
||||||
|
.comparison-view {
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comparison-container {
|
||||||
|
position: relative;
|
||||||
|
width: 60%;
|
||||||
|
max-width: 600px;
|
||||||
|
aspect-ratio: 1;
|
||||||
|
border-radius: 12px;
|
||||||
|
overflow: hidden;
|
||||||
|
background:
|
||||||
|
linear-gradient(45deg, #f1f5f9 25%, transparent 25%),
|
||||||
|
linear-gradient(-45deg, #f1f5f9 25%, transparent 25%),
|
||||||
|
linear-gradient(45deg, transparent 75%, #f1f5f9 75%),
|
||||||
|
linear-gradient(-45deg, transparent 75%, #f1f5f9 75%);
|
||||||
|
background-size: 20px 20px;
|
||||||
|
background-position: 0 0, 0 10px, 10px -10px, -10px 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comparison-image {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: contain;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.original-image {
|
||||||
|
z-index: 1;
|
||||||
|
/* clip-path 通过内联样式动态设置,用于裁剪显示区域 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-image {
|
||||||
|
z-index: 2;
|
||||||
|
/* clip-path 通过内联样式动态设置,用于裁剪显示区域 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 拖动竖线 */
|
||||||
|
.split-line {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
width: 2px;
|
||||||
|
background: transparent;
|
||||||
|
cursor: col-resize;
|
||||||
|
z-index: 10;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
transition: left 0s;
|
||||||
|
user-select: none;
|
||||||
|
pointer-events: auto;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
width: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.dragging {
|
||||||
|
transition: none;
|
||||||
|
cursor: col-resize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.split-line-handle {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
background: rgba(255, 255, 255, 0.95);
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
||||||
|
border: 2px solid #1fc76f;
|
||||||
|
|
||||||
|
i {
|
||||||
|
color: #1fc76f;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 单图视图 */
|
||||||
|
.single-image-view {
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
.image-wrapper {
|
.image-wrapper {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 100%;
|
width: 60%;
|
||||||
|
max-width: 600px;
|
||||||
aspect-ratio: 1;
|
aspect-ratio: 1;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user