From f746b8089f765bed2f1e6b0a9a33130ffc471b9f Mon Sep 17 00:00:00 2001 From: Zhukj <3262118957> Date: Fri, 17 Apr 2026 11:12:01 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E9=A1=B6=E9=83=A8=E5=B7=A5=E5=85=B7?= =?UTF-8?q?=E6=A0=8F=E6=96=B0=E5=A2=9E=E5=8F=A0=E5=8A=A0=E5=9B=BE=E7=89=87?= =?UTF-8?q?=E6=8C=89=E9=92=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- css/style.css | 44 ++++++++ index.html | 3 + js/script.js | 291 ++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 338 insertions(+) diff --git a/css/style.css b/css/style.css index 240371a..7538e8f 100644 --- a/css/style.css +++ b/css/style.css @@ -222,6 +222,50 @@ html, body { overflow: hidden; } +/* 层叠图层 - 位于背景图之上、文字之下 */ +.overlay-layer { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + pointer-events: none; +} + +.overlay-layer .overlay-image { + position: absolute; + pointer-events: auto; + cursor: move; + touch-action: none; +} + +.overlay-layer .overlay-image.selected { + outline: 2px dashed rgba(74, 144, 226, 0.8); + outline-offset: 2px; +} + +.overlay-layer .overlay-image .delete-btn { + position: absolute; + top: -10px; + right: -10px; + width: 20px; + height: 20px; + background: #e74c3c; + color: #fff; + border: none; + border-radius: 50%; + font-size: 12px; + line-height: 20px; + text-align: center; + cursor: pointer; + display: none; + z-index: 10; +} + +.overlay-layer .overlay-image.selected .delete-btn { + display: block; +} + /* 输入框图层 - 与背景图层隔离 */ .preview-input-layer { position: absolute; diff --git a/index.html b/index.html index a9d4112..f612664 100644 --- a/index.html +++ b/index.html @@ -11,6 +11,7 @@
+
@@ -53,6 +54,8 @@
背景图
+ +
diff --git a/js/script.js b/js/script.js index 53acbc3..62a7423 100644 --- a/js/script.js +++ b/js/script.js @@ -2,6 +2,7 @@ (function() { const fileInput = document.getElementById('fileInput'); const addImageBtn = document.getElementById('addImageBtn'); + const addOverlayBtn = document.getElementById('addOverlayBtn'); const saveBtn = document.getElementById('saveBtn'); const imageContainer = document.getElementById('imageContainer'); const bgLayer = document.getElementById('bgLayer'); @@ -18,6 +19,12 @@ const colorPicker = document.getElementById('colorPicker'); const fontPicker = document.getElementById('fontPicker'); const fontSizePicker = document.getElementById('fontSizePicker'); + const overlayLayer = document.getElementById('overlayLayer'); + const overlayInput = document.createElement('input'); + overlayInput.type = 'file'; + overlayInput.accept = 'image/*'; + overlayInput.hidden = true; + document.body.appendChild(overlayInput); const guideH = document.getElementById('guideH'); const guideV = document.getElementById('guideV'); @@ -43,6 +50,20 @@ fileInput.click(); }); + // 层叠图按钮 + addOverlayBtn.addEventListener('click', () => { + overlayInput.click(); + }); + + // 层叠图文件选择 + overlayInput.addEventListener('change', (e) => { + const file = e.target.files[0]; + if (file) { + loadOverlayImage(file); + } + overlayInput.value = ''; + }); + // 文件选择 fileInput.addEventListener('change', (e) => { const file = e.target.files[0]; @@ -139,6 +160,276 @@ reader.readAsDataURL(file); } + // 加载层叠图 + function loadOverlayImage(file) { + const reader = new FileReader(); + reader.onload = (e) => { + const img = new Image(); + img.onload = () => { + createOverlayElement(img); + }; + img.src = e.target.result; + }; + reader.readAsDataURL(file); + } + + // 创建层叠图元素 + function createOverlayElement(img) { + const container = imageContainer; + const containerWidth = container.clientWidth; + const containerHeight = container.clientHeight; + + // 计算初始尺寸(不超过容器1/3) + let width = img.naturalWidth; + let height = img.naturalHeight; + const maxSize = Math.min(containerWidth, containerHeight) / 2; + + if (width > maxSize || height > maxSize) { + if (width > height) { + height = (height / width) * maxSize; + width = maxSize; + } else { + width = (width / height) * maxSize; + height = maxSize; + } + } + + // 创建层叠图元素 + const overlayEl = document.createElement('div'); + overlayEl.className = 'overlay-image'; + overlayEl.dataset.scale = 1; + + const imgEl = document.createElement('img'); + imgEl.src = img.src; + imgEl.style.width = '100%'; + imgEl.style.height = '100%'; + imgEl.style.objectFit = 'contain'; + overlayEl.appendChild(imgEl); + + // 删除按钮 + const deleteBtn = document.createElement('button'); + deleteBtn.className = 'delete-btn'; + deleteBtn.innerHTML = '×'; + deleteBtn.addEventListener('click', (e) => { + e.stopPropagation(); + overlayEl.remove(); + }); + overlayEl.appendChild(deleteBtn); + + // 居中显示 + overlayEl.style.left = (containerWidth / 2 - width / 2) + 'px'; + overlayEl.style.top = (containerHeight / 2 - height / 2) + 'px'; + overlayEl.style.width = width + 'px'; + overlayEl.style.height = height + 'px'; + + // 初始化拖拽和缩放 + initOverlayImage(overlayEl); + + overlayLayer.appendChild(overlayEl); + + // 选中新添加的层叠图 + selectOverlay(overlayEl); + } + + // 初始化层叠图交互 + function initOverlayImage(overlayEl) { + let isDragging = false; + let isResizing = false; + let startX, startY, startLeft, startTop, startWidth, startHeight; + let currentScale = 1; + const minSize = 30; + + // 点击选中 + overlayEl.addEventListener('click', (e) => { + e.stopPropagation(); + selectOverlay(overlayEl); + }); + + // 拖拽移动 + overlayEl.addEventListener('mousedown', handleDragStart); + overlayEl.addEventListener('touchstart', handleDragStart, { passive: false }); + + function handleDragStart(e) { + if (e.target.classList.contains('delete-btn')) return; + + const isTouch = e.type === 'touchstart'; + const clientX = isTouch ? e.touches[0].clientX : e.clientX; + const clientY = isTouch ? e.touches[0].clientY : e.clientY; + + isDragging = true; + startX = clientX; + startY = clientY; + startLeft = overlayEl.offsetLeft; + startTop = overlayEl.offsetTop; + + overlayEl.classList.add('dragging'); + + document.addEventListener('mousemove', handleDragMove); + document.addEventListener('mouseup', handleDragEnd); + document.addEventListener('touchmove', handleDragMove, { passive: false }); + document.addEventListener('touchend', handleDragEnd); + + e.preventDefault(); + } + + function handleDragMove(e) { + if (!isDragging) return; + + const isTouch = e.type === 'touchmove'; + const clientX = isTouch ? e.touches[0].clientX : e.clientX; + const clientY = isTouch ? e.touches[0].clientY : e.clientY; + + const dx = clientX - startX; + const dy = clientY - startY; + + let newLeft = startLeft + dx; + let newTop = startTop + dy; + + // 边界限制 + newLeft = Math.max(-overlayEl.offsetWidth + minSize, Math.min(newLeft, imageContainer.offsetWidth - minSize)); + newTop = Math.max(-overlayEl.offsetHeight + minSize, Math.min(newTop, imageContainer.offsetHeight - minSize)); + + overlayEl.style.left = newLeft + 'px'; + overlayEl.style.top = newTop + 'px'; + + e.preventDefault(); + } + + function handleDragEnd() { + isDragging = false; + overlayEl.classList.remove('dragging'); + + document.removeEventListener('mousemove', handleDragMove); + document.removeEventListener('mouseup', handleDragEnd); + document.removeEventListener('touchmove', handleDragMove); + document.removeEventListener('touchend', handleDragEnd); + } + + // 缩放控制(四角) + const corners = ['tl', 'tr', 'bl', 'br']; + corners.forEach(corner => { + const handle = document.createElement('div'); + handle.className = 'resize-handle resize-' + corner; + handle.style.cssText = ` + position: absolute; + width: 12px; + height: 12px; + background: #4a90e2; + border: 1px solid #fff; + `; + + switch(corner) { + case 'tl': handle.style.top = '-6px'; handle.style.left = '-6px'; handle.style.cursor = 'nw-resize'; break; + case 'tr': handle.style.top = '-6px'; handle.style.right = '-6px'; handle.style.cursor = 'ne-resize'; break; + case 'bl': handle.style.bottom = '-6px'; handle.style.left = '-6px'; handle.style.cursor = 'sw-resize'; break; + case 'br': handle.style.bottom = '-6px'; handle.style.right = '-6px'; handle.style.cursor = 'se-resize'; break; + } + + handle.addEventListener('mousedown', startResize); + handle.addEventListener('touchstart', startResize, { passive: false }); + + overlayEl.appendChild(handle); + + function startResize(e) { + e.stopPropagation(); + isResizing = true; + const isTouch = e.type === 'touchstart'; + const clientX = isTouch ? e.touches[0].clientX : e.clientX; + const clientY = isTouch ? e.touches[0].clientY : e.clientY; + + startX = clientX; + startY = clientY; + startLeft = overlayEl.offsetLeft; + startTop = overlayEl.offsetTop; + startWidth = overlayEl.offsetWidth; + startHeight = overlayEl.offsetHeight; + + document.addEventListener('mousemove', handleResize); + document.addEventListener('mouseup', endResize); + document.addEventListener('touchmove', handleResize, { passive: false }); + document.addEventListener('touchend', endResize); + + e.preventDefault(); + } + + function handleResize(e) { + if (!isResizing) return; + + const isTouch = e.type === 'touchmove'; + const clientX = isTouch ? e.touches[0].clientX : e.clientX; + const clientY = isTouch ? e.touches[0].clientY : e.clientY; + + const dx = clientX - startX; + const dy = clientY - startY; + + let newWidth = startWidth; + let newHeight = startHeight; + let newLeft = startLeft; + let newTop = startTop; + + const aspectRatio = startWidth / startHeight; + + // 根据角落调整尺寸 + if (corner === 'br') { + newWidth = Math.max(minSize, startWidth + dx); + newHeight = newWidth / aspectRatio; + } else if (corner === 'bl') { + newWidth = Math.max(minSize, startWidth - dx); + newHeight = newWidth / aspectRatio; + newLeft = startLeft - (newWidth - startWidth); + } else if (corner === 'tr') { + newWidth = Math.max(minSize, startWidth + dx); + newHeight = newWidth / aspectRatio; + newTop = startTop - (newHeight - startHeight); + } else if (corner === 'tl') { + newWidth = Math.max(minSize, startWidth - dx); + newHeight = newWidth / aspectRatio; + newLeft = startLeft - (newWidth - startWidth); + newTop = startTop - (newHeight - startHeight); + } + + overlayEl.style.width = newWidth + 'px'; + overlayEl.style.height = newHeight + 'px'; + overlayEl.style.left = newLeft + 'px'; + overlayEl.style.top = newTop + 'px'; + + e.preventDefault(); + } + + function endResize() { + isResizing = false; + document.removeEventListener('mousemove', handleResize); + document.removeEventListener('mouseup', endResize); + document.removeEventListener('touchmove', handleResize); + document.removeEventListener('touchend', endResize); + } + }); + } + + // 选中层叠图 + let selectedOverlay = null; + function selectOverlay(overlayEl) { + if (selectedOverlay) { + selectedOverlay.classList.remove('selected'); + } + selectedOverlay = overlayEl; + overlayEl.classList.add('selected'); + } + + // 点击背景取消选中层叠图 + imageContainer.addEventListener('click', (e) => { + if (e.target === imageContainer || + e.target === bgLayer || + e.target === bgImage || + e.target === emptyTip || + e.target.closest('.preview-bg-layer')) { + if (selectedOverlay) { + selectedOverlay.classList.remove('selected'); + selectedOverlay = null; + } + } + }); + // 计算图片滑动边界 function calculateImageBounds() { if (!currentImage) return;