From 2752943c770a80222ea2a038ec4fce1940d21160 Mon Sep 17 00:00:00 2001 From: Zhukj <3262118957> Date: Sat, 18 Apr 2026 16:14:27 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E8=83=8C=E6=99=AF=E5=9B=BE=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E7=BC=A9=E6=94=BE+=E9=80=8F=E6=98=8E=E5=BA=A6?= =?UTF-8?q?=E7=BC=96=E8=BE=91=EF=BC=8C=E4=B8=89=E5=9B=BE=E6=96=87=E5=85=B1?= =?UTF-8?q?=E7=94=A8=E7=BB=9F=E4=B8=80=E5=88=87=E6=8D=A2=E5=B7=A5=E5=85=B7?= =?UTF-8?q?=E6=A0=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 + css/style.css | 8 +++ index.html | 14 ++++ js/script.js | 175 +++++++++++++++++++++++++++++++++++++++++++++++--- 4 files changed, 189 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index c31ae7e..61f35c3 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,8 @@ 18. 可添加多个文本框,以及文本框删除功能 19. 添加图片编辑器工具栏,图片编辑器与文本编辑器共用同一位置工具栏,选中对应元素切换对应编辑面板,点击空白处隐藏面板 20. 当选择文字的时候工具栏对应出现文字编辑器,当选择图片的时候工具栏对应出现图片编辑器,点击其他位置时隐藏工具栏,两个编辑栏的宽高保持和文本编辑器工具栏一致 +21. 调整图文工具栏布局,解决顶部遮挡问题,图片透明度滑块添加百分比 +22. 给背景图添加编辑器,与层叠图编辑器文本编辑器通用一个工具栏,背景图工具栏功能为:背景图可以放大缩小,可以调节透明度,其余功能不受影响,点击层叠图只显示当前编辑器工具栏 diff --git a/css/style.css b/css/style.css index b003499..5aefb9e 100644 --- a/css/style.css +++ b/css/style.css @@ -173,6 +173,14 @@ html, body { margin: 0; } +.scale-slider { + width: 120px; + height: 40px; + cursor: pointer; + accent-color: #4a90e2; + margin: 0; +} + /* 字号滚轮选择器 */ .roller-picker { display: flex; diff --git a/index.html b/index.html index f42207e..bc15ada 100644 --- a/index.html +++ b/index.html @@ -55,6 +55,20 @@ 100% + +
+ +
+ +
diff --git a/js/script.js b/js/script.js index 003c70d..2695983 100644 --- a/js/script.js +++ b/js/script.js @@ -17,8 +17,13 @@ const editorToolbar = document.getElementById('editorToolbar'); const textTools = document.getElementById('textTools'); const imageTools = document.getElementById('imageTools'); + const bgTools = document.getElementById('bgTools'); const opacitySlider = document.getElementById('opacitySlider'); const opacityValue = document.getElementById('opacityValue'); + const bgScaleSlider = document.getElementById('bgScaleSlider'); + const bgScaleValue = document.getElementById('bgScaleValue'); + const bgOpacitySlider = document.getElementById('bgOpacitySlider'); + const bgOpacityValue = document.getElementById('bgOpacityValue'); const boldBtn = document.getElementById('boldBtn'); const italicBtn = document.getElementById('italicBtn'); const colorPicker = document.getElementById('colorPicker'); @@ -34,6 +39,8 @@ const guideV = document.getElementById('guideV'); let currentImage = null; + let bgImageScale = 1; + let bgImageOpacity = 1; let selectedInput = null; let editingInput = null; let longPressTimer = null; @@ -45,6 +52,7 @@ let imageStartOffsetX = 0; let minOffsetX = 0; let maxOffsetX = 0; + let isInteractingWithBgImage = false; // 初始化 init(); @@ -113,6 +121,20 @@ } }); + // 背景图缩放滑块 + bgScaleSlider.addEventListener('input', (e) => { + bgImageScale = e.target.value / 100; + bgScaleValue.textContent = e.target.value + '%'; + updateBgImage(); + }); + + // 背景图透明度滑块 + bgOpacitySlider.addEventListener('input', (e) => { + bgImageOpacity = e.target.value / 100; + bgOpacityValue.textContent = e.target.value + '%'; + bgImage.style.opacity = bgImageOpacity; + }); + // 点击空白区域取消选中 document.addEventListener('click', (e) => { // 只有点击真正的空白区域(不是输入框,不是图片容器,不是工具栏)才取消选中 @@ -128,6 +150,10 @@ // 点击容器空白处取消选中 function handleContainerClick(e) { + if (isInteractingWithBgImage) { + return; + } + const isBackground = e.target === imageContainer || e.target === bgLayer || e.target === bgImage || @@ -202,7 +228,15 @@ calculateImageBounds(); // 背景图选择后默认居中显示 imageOffsetX = 0; - updateImagePosition(); + // 重置缩放 + bgImageScale = 1; + bgScaleSlider.value = 100; + bgScaleValue.textContent = '100%'; + // 重置透明度 + bgImageOpacity = 1; + bgOpacitySlider.value = 100; + bgOpacityValue.textContent = '100%'; + updateBgImage(); }; img.src = e.target.result; }; @@ -523,6 +557,7 @@ editorToolbar.classList.add('active'); textTools.classList.remove('active'); imageTools.classList.add('active'); + bgTools.classList.remove('active'); // 更新透明度滑块为当前层叠图的透明度 const currentOpacity = overlayEl.style.opacity || '1'; @@ -569,13 +604,10 @@ } } - // 更新图片位置 - function updateImagePosition() { + // 更新背景图(缩放和透明度) + function updateBgImage() { if (!currentImage) return; - // 限制滑动范围,确保不露出白板 - imageOffsetX = Math.max(minOffsetX, Math.min(maxOffsetX, imageOffsetX)); - const containerWidth = imageContainer.clientWidth; const containerHeight = imageContainer.clientHeight; const imgRatio = currentImage.naturalWidth / currentImage.naturalHeight; @@ -591,11 +623,71 @@ imgHeight = imgWidth / imgRatio; } - // 应用位置 - const baseLeft = (containerWidth - imgWidth) / 2; - bgImage.style.width = imgWidth + 'px'; - bgImage.style.height = imgHeight + 'px'; + // 应用缩放 + bgImage.style.width = (imgWidth * bgImageScale) + 'px'; + bgImage.style.height = (imgHeight * bgImageScale) + 'px'; + + // 应用透明度 + bgImage.style.opacity = bgImageOpacity; + + // 重新计算边界 + const scaledWidth = imgWidth * bgImageScale; + const scaledHeight = imgHeight * bgImageScale; + if (scaledWidth > containerWidth) { + minOffsetX = -(scaledWidth - containerWidth) / 2; + maxOffsetX = (scaledWidth - containerWidth) / 2; + } else { + minOffsetX = 0; + maxOffsetX = 0; + } + // 限制当前位置在新范围内 + imageOffsetX = Math.max(minOffsetX, Math.min(maxOffsetX, imageOffsetX)); + + // 重新应用位置(居中 + 偏移) + const baseLeft = (containerWidth - scaledWidth) / 2; bgImage.style.left = (baseLeft + imageOffsetX) + 'px'; + bgImage.style.top = ((containerHeight - scaledHeight) / 2) + 'px'; + } + + // 更新图片位置 + function updateImagePosition() { + if (!currentImage) return; + + const containerWidth = imageContainer.clientWidth; + const containerHeight = imageContainer.clientHeight; + const imgRatio = currentImage.naturalWidth / currentImage.naturalHeight; + + let imgWidth, imgHeight; + + if (imgRatio > containerWidth / containerHeight) { + imgHeight = containerHeight; + imgWidth = imgHeight * imgRatio; + } else { + imgWidth = containerWidth; + imgHeight = imgWidth / imgRatio; + } + + // 应用缩放 + const scaledWidth = imgWidth * bgImageScale; + const scaledHeight = imgHeight * bgImageScale; + + // 限制滑动范围 + if (scaledWidth > containerWidth) { + minOffsetX = -(scaledWidth - containerWidth) / 2; + maxOffsetX = (scaledWidth - containerWidth) / 2; + } else { + minOffsetX = 0; + maxOffsetX = 0; + } + imageOffsetX = Math.max(minOffsetX, Math.min(maxOffsetX, imageOffsetX)); + + // 应用位置 + const baseLeft = (containerWidth - scaledWidth) / 2; + bgImage.style.width = scaledWidth + 'px'; + bgImage.style.height = scaledHeight + 'px'; + bgImage.style.left = (baseLeft + imageOffsetX) + 'px'; + bgImage.style.top = ((containerHeight - scaledHeight) / 2) + 'px'; + bgImage.style.opacity = bgImageOpacity; bgImage.style.transform = 'none'; } @@ -613,6 +705,53 @@ document.addEventListener('mouseup', handleImageEnd); document.addEventListener('touchend', handleImageEnd); + // 点击背景图显示工具栏 + bgImage.addEventListener('click', (e) => { + if (!currentImage) return; + e.stopPropagation(); + + isInteractingWithBgImage = true; + + // 显示背景图工具栏 + editorToolbar.classList.add('active'); + textTools.classList.remove('active'); + imageTools.classList.remove('active'); + bgTools.classList.add('active'); + + // 同步滑块值 + bgScaleSlider.value = bgImageScale * 100; + bgScaleValue.textContent = Math.round(bgImageScale * 100) + '%'; + bgOpacitySlider.value = bgImageOpacity * 100; + bgOpacityValue.textContent = Math.round(bgImageOpacity * 100) + '%'; + + setTimeout(() => { + isInteractingWithBgImage = false; + }, 100); + }); + + bgLayer.addEventListener('click', (e) => { + if (!currentImage) return; + e.stopPropagation(); + + isInteractingWithBgImage = true; + + // 显示背景图工具栏 + editorToolbar.classList.add('active'); + textTools.classList.remove('active'); + imageTools.classList.remove('active'); + bgTools.classList.add('active'); + + // 同步滑块值 + bgScaleSlider.value = bgImageScale * 100; + bgScaleValue.textContent = Math.round(bgImageScale * 100) + '%'; + bgOpacitySlider.value = bgImageOpacity * 100; + bgOpacityValue.textContent = Math.round(bgImageOpacity * 100) + '%'; + + setTimeout(() => { + isInteractingWithBgImage = false; + }, 100); + }); + function handleImageStart(e) { // 如果点击的是输入框,不处理图片滑动 if (e.target.closest('.text-input')) { @@ -642,9 +781,22 @@ } isDraggingImage = true; + isInteractingWithBgImage = true; imageStartX = clientX; imageStartOffsetX = imageOffsetX; + // 显示背景图工具栏 + editorToolbar.classList.add('active'); + textTools.classList.remove('active'); + imageTools.classList.remove('active'); + bgTools.classList.add('active'); + + // 同步滑块值 + bgScaleSlider.value = bgImageScale * 100; + bgScaleValue.textContent = Math.round(bgImageScale * 100) + '%'; + bgOpacitySlider.value = bgImageOpacity * 100; + bgOpacityValue.textContent = Math.round(bgImageOpacity * 100) + '%'; + if (isTouch) { e.preventDefault(); } @@ -670,6 +822,7 @@ function handleImageEnd() { isDraggingImage = false; + isInteractingWithBgImage = false; } } @@ -863,6 +1016,7 @@ editorToolbar.classList.add('active'); textTools.classList.add('active'); imageTools.classList.remove('active'); + bgTools.classList.remove('active'); // 更新工具栏状态 updateToolbarState(element); @@ -885,6 +1039,7 @@ editorToolbar.classList.add('active'); textTools.classList.add('active'); imageTools.classList.remove('active'); + bgTools.classList.remove('active'); // 更新工具栏状态 updateToolbarState(element);