diff --git a/README.md b/README.md index 5817034..5351728 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,8 @@ 11. 添加图片尺寸不一致时可以左右滑动图片调整位置,并且图片反转拖拽 12. 电脑端右键保存/复制/预览图片,手机端长按屏幕弹窗保存/复制/预览图片,可预览 13. 顶部工具栏下方16px的位置,固定放置文本编辑器工具栏,加粗,斜体,字体颜色,字体,字号,可编辑文字样式 -14. +14. 字号逻辑调整,输入框字号只在自身原本字号上增减 + diff --git a/css/style.css b/css/style.css index 6da2311..62db4aa 100644 --- a/css/style.css +++ b/css/style.css @@ -355,3 +355,31 @@ html, body { .context-menu-item:active { background: #e8e8e8; } + +/* 拖拽辅助线 */ +.guide-line { + position: absolute; + pointer-events: none; + z-index: 1000; + display: none; +} + +.guide-horizontal { + left: 0; + right: 0; + height: 1px; + background: #4a90e2; + box-shadow: 0 0 4px rgba(74, 144, 226, 0.5); +} + +.guide-vertical { + top: 0; + bottom: 0; + width: 1px; + background: #4a90e2; + box-shadow: 0 0 4px rgba(74, 144, 226, 0.5); +} + +.guide-line.visible { + display: block; +} diff --git a/index.html b/index.html index c36fb5b..a9d4112 100644 --- a/index.html +++ b/index.html @@ -53,6 +53,9 @@
背景图
+ +
+
diff --git a/js/script.js b/js/script.js index 08997f9..a8d429a 100644 --- a/js/script.js +++ b/js/script.js @@ -18,6 +18,8 @@ const colorPicker = document.getElementById('colorPicker'); const fontPicker = document.getElementById('fontPicker'); const fontSizePicker = document.getElementById('fontSizePicker'); + const guideH = document.getElementById('guideH'); + const guideV = document.getElementById('guideV'); let currentImage = null; let selectedInput = null; @@ -356,8 +358,20 @@ if (isDragging) { const dx = clientX - startX; const dy = clientY - startY; - element.style.left = (startLeft + dx) + 'px'; - element.style.top = (startTop + dy) + 'px'; + const newLeft = startLeft + dx; + const newTop = startTop + dy; + + // 检测对齐并吸附 + const snapped = getSnapPosition(element, newLeft, newTop); + element.style.left = snapped.left + 'px'; + element.style.top = snapped.top + 'px'; + + // 显示对齐辅助线 + if (snapped.showGuide) { + showAlignmentGuides(element); + } else { + hideGuides(); + } } // 阻止事件冒泡,避免触发图片滑动 @@ -385,6 +399,7 @@ if (isDragging) { isDragging = false; element.classList.remove('dragging'); + hideGuides(); } // 阻止事件冒泡 @@ -507,6 +522,146 @@ } } + // 获取吸附后的位置 + function getSnapPosition(draggedEl, newLeft, newTop) { + const container = imageContainer; + const containerRect = container.getBoundingClientRect(); + + const elWidth = draggedEl.offsetWidth; + const elHeight = draggedEl.offsetHeight; + + // 计算元素各参考点相对容器的位置 + const elCenterX = newLeft + elWidth / 2; + const elCenterY = newTop + elHeight / 2; + const elLeft = newLeft; + const elRight = newLeft + elWidth; + const elTop = newTop; + const elBottom = newTop + elHeight; + + // 容器尺寸 + const cWidth = containerRect.width; + const cHeight = containerRect.height; + const cCenterX = cWidth / 2; + const cCenterY = cHeight / 2; + + const threshold = 5; + let snapLeft = newLeft; + let snapTop = newTop; + let showGuide = false; + + // 水平垂直分别检测 + let snapX = false; + let snapY = false; + let snapXPos = null; + let snapYPos = null; + + // 检测容器中心对齐 + if (Math.abs(elCenterX - cCenterX) < threshold) { + snapX = true; + snapXPos = cCenterX; + } + if (Math.abs(elCenterY - cCenterY) < threshold) { + snapY = true; + snapYPos = cCenterY; + } + + // 检测容器边缘对齐 + if (Math.abs(elLeft) < threshold) { + snapX = true; + snapXPos = 0; + } + if (Math.abs(elRight - cWidth) < threshold) { + snapX = true; + snapXPos = cWidth; + } + if (Math.abs(elTop) < threshold) { + snapY = true; + snapYPos = 0; + } + if (Math.abs(elBottom - cHeight) < threshold) { + snapY = true; + snapYPos = cHeight; + } + + // 检测其他元素对齐 + const otherInputs = [input1, input2].filter(el => el && el !== draggedEl); + + for (const otherEl of otherInputs) { + if (!otherEl) continue; + + const oLeft = otherEl.offsetLeft; + const oTop = otherEl.offsetTop; + const oWidth = otherEl.offsetWidth; + const oHeight = otherEl.offsetHeight; + const oRight = oLeft + oWidth; + const oBottom = oTop + oHeight; + const oCenterX = oLeft + oWidth / 2; + const oCenterY = oTop + oHeight / 2; + + // 垂直边缘对齐 + if (Math.abs(elLeft - oLeft) < threshold) { + snapX = true; + snapXPos = oLeft; + } + if (Math.abs(elRight - oRight) < threshold) { + snapX = true; + snapXPos = oRight - elWidth; + } + if (Math.abs(elLeft - oRight) < threshold) { + snapX = true; + snapXPos = oRight; + } + if (Math.abs(elRight - oLeft) < threshold) { + snapX = true; + snapXPos = oLeft - elWidth; + } + + // 水平边缘对齐 + if (Math.abs(elTop - oTop) < threshold) { + snapY = true; + snapYPos = oTop; + } + if (Math.abs(elBottom - oBottom) < threshold) { + snapY = true; + snapYPos = oBottom - elHeight; + } + if (Math.abs(elTop - oBottom) < threshold) { + snapY = true; + snapYPos = oBottom; + } + if (Math.abs(elBottom - oTop) < threshold) { + snapY = true; + snapYPos = oTop - elHeight; + } + + // 中心对齐 + if (Math.abs(elCenterX - oCenterX) < threshold) { + snapX = true; + snapXPos = oCenterX - elWidth / 2; + } + if (Math.abs(elCenterY - oCenterY) < threshold) { + snapY = true; + snapYPos = oCenterY - elHeight / 2; + } + } + + // 应用吸附 + if (snapX && snapXPos !== null) { + snapLeft = snapXPos; + showGuide = true; + } + if (snapY && snapYPos !== null) { + snapTop = snapYPos; + showGuide = true; + } + + // 边界限制 + snapLeft = Math.max(0, Math.min(snapLeft, cWidth - elWidth)); + snapTop = Math.max(0, Math.min(snapTop, cHeight - elHeight)); + + return { left: snapLeft, top: snapTop, showGuide }; + } + // RGB转十六进制 function rgbToHex(rgb) { if (rgb.startsWith('#')) return rgb; @@ -515,6 +670,122 @@ return '#' + [match[1], match[2], match[3]].map(x => parseInt(x).toString(16).padStart(2, '0')).join(''); } + // 对齐辅助线 - 水平和垂直居中、对齐其他元素 + function showAlignmentGuides(draggedEl) { + const container = imageContainer; + const cWidth = container.offsetWidth; + const cHeight = container.offsetHeight; + + const elWidth = draggedEl.offsetWidth; + const elHeight = draggedEl.offsetHeight; + const elLeft = draggedEl.offsetLeft; + const elTop = draggedEl.offsetTop; + const elRight = elLeft + elWidth; + const elBottom = elTop + elHeight; + const elCenterX = elLeft + elWidth / 2; + const elCenterY = elTop + elHeight / 2; + + const threshold = 5; + let guideHPos = null; + let guideVPos = null; + + // 容器中心对齐 + const cCenterX = cWidth / 2; + const cCenterY = cHeight / 2; + + if (Math.abs(elCenterX - cCenterX) < threshold) { + guideVPos = cCenterX; + } + if (Math.abs(elCenterY - cCenterY) < threshold) { + guideHPos = cCenterY; + } + + // 容器边缘对齐 + if (Math.abs(elLeft) < threshold) { + guideVPos = 0; + } + if (Math.abs(elRight - cWidth) < threshold) { + guideVPos = cWidth; + } + if (Math.abs(elTop) < threshold) { + guideHPos = 0; + } + if (Math.abs(elBottom - cHeight) < threshold) { + guideHPos = cHeight; + } + + // 其他输入框对齐检测 + const otherInputs = [input1, input2].filter(el => el && el !== draggedEl); + + for (const otherEl of otherInputs) { + if (!otherEl) continue; + + const oLeft = otherEl.offsetLeft; + const oTop = otherEl.offsetTop; + const oWidth = otherEl.offsetWidth; + const oHeight = otherEl.offsetHeight; + const oRight = oLeft + oWidth; + const oBottom = oTop + oHeight; + const oCenterX = oLeft + oWidth / 2; + const oCenterY = oTop + oHeight / 2; + + // 垂直边缘对齐 + if (Math.abs(elLeft - oLeft) < threshold) { + guideVPos = oLeft; + } + if (Math.abs(elRight - oRight) < threshold) { + guideVPos = oRight; + } + if (Math.abs(elLeft - oRight) < threshold) { + guideVPos = oRight; + } + if (Math.abs(elRight - oLeft) < threshold) { + guideVPos = oLeft; + } + + // 水平边缘对齐 + if (Math.abs(elTop - oTop) < threshold) { + guideHPos = oTop; + } + if (Math.abs(elBottom - oBottom) < threshold) { + guideHPos = oBottom; + } + if (Math.abs(elTop - oBottom) < threshold) { + guideHPos = oBottom; + } + if (Math.abs(elBottom - oTop) < threshold) { + guideHPos = oTop; + } + + // 中心对齐 + if (Math.abs(elCenterX - oCenterX) < threshold) { + guideVPos = oCenterX; + } + if (Math.abs(elCenterY - oCenterY) < threshold) { + guideHPos = oCenterY; + } + } + + if (guideHPos !== null) { + guideH.style.top = guideHPos + 'px'; + guideH.classList.add('visible'); + } else { + guideH.classList.remove('visible'); + } + + if (guideVPos !== null) { + guideV.style.left = guideVPos + 'px'; + guideV.classList.add('visible'); + } else { + guideV.classList.remove('visible'); + } + } + + function hideGuides() { + guideH.classList.remove('visible'); + guideV.classList.remove('visible'); + } + // 取消所有选中 function deselectAll() { if (editingInput) {