From cac901e235ab5a5947756adb3514575d21ee53c3 Mon Sep 17 00:00:00 2001 From: jingrow Date: Wed, 19 Nov 2025 06:59:55 +0800 Subject: [PATCH] improve RemoveBackground comparison view and split line styling --- .../src/views/tools/RemoveBackground.vue | 254 ++++++++++++++---- 1 file changed, 198 insertions(+), 56 deletions(-) diff --git a/apps/jingrow/frontend/src/views/tools/RemoveBackground.vue b/apps/jingrow/frontend/src/views/tools/RemoveBackground.vue index 3fee207..eecadb7 100644 --- a/apps/jingrow/frontend/src/views/tools/RemoveBackground.vue +++ b/apps/jingrow/frontend/src/views/tools/RemoveBackground.vue @@ -39,69 +39,67 @@

{{ t('Image Preview') }}

- +
+ + + {{ t('Download') }} + + +
-
- -
-
{{ t('Original') }}
-
+ +
+
+ +
Original -
-
-

{{ t('Processing...') }}

-
-
- - -
-
{{ t('Background Removed') }}
-
-
- -

{{ t('Result will appear here') }}

-
- Result -
-
-

{{ t('Processing...') }}

+ + +
+ Result +
+ + +
+
+
- - -
- - - {{ processing ? t('Processing...') : t('Remove Background') }} - - - - - {{ t('Download') }} - + + +
+
+ Original +
+
+

{{ t('Processing...') }}

+
+
@@ -157,6 +155,9 @@ const resultImageUrl = computed(() => { // 状态 const isDragging = ref(false) const processing = ref(false) +const splitPosition = ref(50) // 分割线位置(百分比) +const comparisonContainerRef = ref(null) +const isDraggingSplitLine = ref(false) // 触发文件选择 const triggerFileInput = () => { @@ -209,11 +210,14 @@ const processFile = (file: File) => { uploadedImage.value = file resultImage.value = '' + splitPosition.value = 50 // 重置分割线位置 // 创建预览URL const reader = new FileReader() reader.onload = (e) => { uploadedImageUrl.value = e.target?.result as string + // 上传后自动触发去背景 + handleRemoveBackground() } reader.readAsDataURL(file) } @@ -223,11 +227,36 @@ const resetUpload = () => { uploadedImage.value = null uploadedImageUrl.value = '' resultImage.value = '' + splitPosition.value = 50 if (fileInputRef.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 () => { // 检查用户是否已登录 @@ -378,6 +407,14 @@ const handleDownload = () => { width: 100%; padding: 16px; min-height: 100vh; + display: flex; + flex-direction: column; + align-items: center; +} + +.remove-background-page > .page-content { + width: 100%; + max-width: 1000px; } .page-header { @@ -592,9 +629,114 @@ h1 { 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 { position: relative; - width: 100%; + width: 60%; + max-width: 600px; aspect-ratio: 1; border-radius: 8px; overflow: hidden;