feat: 顶部工具栏新增叠加图片按钮

This commit is contained in:
Zhukj
2026-04-17 11:12:01 +08:00
parent 5ff4a21857
commit f746b8089f
3 changed files with 338 additions and 0 deletions

View File

@@ -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;

View File

@@ -11,6 +11,7 @@
<!-- 顶部工具栏 -->
<div class="toolbar">
<button class="btn" id="addImageBtn">添加图片</button>
<button class="btn" id="addOverlayBtn">层叠图</button>
<button class="btn btn-primary" id="saveBtn">保存/预览</button>
</div>
@@ -53,6 +54,8 @@
<div class="preview-bg-layer" id="bgLayer">
<img id="bgImage" src="" alt="背景图">
</div>
<!-- 层叠图图层 -->
<div class="overlay-layer" id="overlayLayer"></div>
<!-- 拖拽辅助线 -->
<div class="guide-line guide-horizontal" id="guideH"></div>
<div class="guide-line guide-vertical" id="guideV"></div>

View File

@@ -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;