feat: 为输入框添加拖动对齐辅助线,优化排版定位体验
This commit is contained in:
@@ -51,7 +51,8 @@
|
|||||||
11. 添加图片尺寸不一致时可以左右滑动图片调整位置,并且图片反转拖拽
|
11. 添加图片尺寸不一致时可以左右滑动图片调整位置,并且图片反转拖拽
|
||||||
12. 电脑端右键保存/复制/预览图片,手机端长按屏幕弹窗保存/复制/预览图片,可预览
|
12. 电脑端右键保存/复制/预览图片,手机端长按屏幕弹窗保存/复制/预览图片,可预览
|
||||||
13. 顶部工具栏下方16px的位置,固定放置文本编辑器工具栏,加粗,斜体,字体颜色,字体,字号,可编辑文字样式
|
13. 顶部工具栏下方16px的位置,固定放置文本编辑器工具栏,加粗,斜体,字体颜色,字体,字号,可编辑文字样式
|
||||||
14.
|
14. 字号逻辑调整,输入框字号只在自身原本字号上增减
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -355,3 +355,31 @@ html, body {
|
|||||||
.context-menu-item:active {
|
.context-menu-item:active {
|
||||||
background: #e8e8e8;
|
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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -53,6 +53,9 @@
|
|||||||
<div class="preview-bg-layer" id="bgLayer">
|
<div class="preview-bg-layer" id="bgLayer">
|
||||||
<img id="bgImage" src="" alt="背景图">
|
<img id="bgImage" src="" alt="背景图">
|
||||||
</div>
|
</div>
|
||||||
|
<!-- 拖拽辅助线 -->
|
||||||
|
<div class="guide-line guide-horizontal" id="guideH"></div>
|
||||||
|
<div class="guide-line guide-vertical" id="guideV"></div>
|
||||||
<!-- 输入框图层 -->
|
<!-- 输入框图层 -->
|
||||||
<div class="preview-input-layer">
|
<div class="preview-input-layer">
|
||||||
<!-- 文字输入框1 - 右下角 -->
|
<!-- 文字输入框1 - 右下角 -->
|
||||||
|
|||||||
275
js/script.js
275
js/script.js
@@ -18,6 +18,8 @@
|
|||||||
const colorPicker = document.getElementById('colorPicker');
|
const colorPicker = document.getElementById('colorPicker');
|
||||||
const fontPicker = document.getElementById('fontPicker');
|
const fontPicker = document.getElementById('fontPicker');
|
||||||
const fontSizePicker = document.getElementById('fontSizePicker');
|
const fontSizePicker = document.getElementById('fontSizePicker');
|
||||||
|
const guideH = document.getElementById('guideH');
|
||||||
|
const guideV = document.getElementById('guideV');
|
||||||
|
|
||||||
let currentImage = null;
|
let currentImage = null;
|
||||||
let selectedInput = null;
|
let selectedInput = null;
|
||||||
@@ -356,8 +358,20 @@
|
|||||||
if (isDragging) {
|
if (isDragging) {
|
||||||
const dx = clientX - startX;
|
const dx = clientX - startX;
|
||||||
const dy = clientY - startY;
|
const dy = clientY - startY;
|
||||||
element.style.left = (startLeft + dx) + 'px';
|
const newLeft = startLeft + dx;
|
||||||
element.style.top = (startTop + dy) + 'px';
|
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) {
|
if (isDragging) {
|
||||||
isDragging = false;
|
isDragging = false;
|
||||||
element.classList.remove('dragging');
|
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转十六进制
|
// RGB转十六进制
|
||||||
function rgbToHex(rgb) {
|
function rgbToHex(rgb) {
|
||||||
if (rgb.startsWith('#')) return 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('');
|
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() {
|
function deselectAll() {
|
||||||
if (editingInput) {
|
if (editingInput) {
|
||||||
|
|||||||
Reference in New Issue
Block a user