// 极简邀请函制作工具 (function() { const fileInput = document.getElementById('fileInput'); const addImageBtn = document.getElementById('addImageBtn'); const saveBtn = document.getElementById('saveBtn'); const imageContainer = document.getElementById('imageContainer'); const bgImage = document.getElementById('bgImage'); const emptyTip = document.getElementById('emptyTip'); const input1 = document.getElementById('input1'); const input2 = document.getElementById('input2'); const exportCanvas = document.getElementById('exportCanvas'); const longPressTip = document.getElementById('longPressTip'); const canvasArea = document.getElementById('canvasArea'); let currentImage = null; let selectedInput = null; let editingInput = null; let longPressTimer = null; // 初始化 init(); function init() { // 添加图片按钮 addImageBtn.addEventListener('click', () => { fileInput.click(); }); // 文件选择 fileInput.addEventListener('change', (e) => { const file = e.target.files[0]; if (file) { loadImage(file); } fileInput.value = ''; }); // 保存按钮 saveBtn.addEventListener('click', saveImage); // 初始化输入框 initInput(input1); initInput(input2); // 点击外部取消选中 document.addEventListener('click', (e) => { if (!e.target.closest('.text-input') && !e.target.closest('.image-container')) { deselectAll(); } }); // 初始化长按保存 initLongPressSave(); // 初始化右键菜单 initContextMenu(); // 显示长按提示 setTimeout(() => { longPressTip.classList.add('show'); setTimeout(() => { longPressTip.classList.remove('show'); }, 3000); }, 1000); } // 加载图片 function loadImage(file) { const reader = new FileReader(); reader.onload = (e) => { const img = new Image(); img.onload = () => { currentImage = img; bgImage.src = img.src; imageContainer.classList.remove('empty'); emptyTip.classList.add('hidden'); }; img.src = e.target.result; }; reader.readAsDataURL(file); } // 初始化输入框 - 每个输入框独立管理自己的拖拽状态 function initInput(element) { // 每个输入框独立的状态变量 let isMouseDown = false; let isDragging = false; let dragTimer = null; let startX, startY, startLeft, startTop; let clickStartTime = 0; let clickStartX = 0; let clickStartY = 0; let hasMoved = false; // 鼠标/触摸按下 element.addEventListener('mousedown', handleStart); element.addEventListener('touchstart', handleStart, { passive: false }); function handleStart(e) { const isTouch = e.type === 'touchstart'; const clientX = isTouch ? e.touches[0].clientX : e.clientX; const clientY = isTouch ? e.touches[0].clientY : e.clientY; // 标记鼠标/手指按下 isMouseDown = true; clickStartTime = Date.now(); clickStartX = clientX; clickStartY = clientY; hasMoved = false; isDragging = false; // 如果正在编辑此输入框,不处理 if (editingInput === element) { return; } // 选中此输入框 selectInput(element); startX = clientX; startY = clientY; startLeft = element.offsetLeft; startTop = element.offsetTop; // 延迟判断是否为拖拽(150ms后判定为拖拽) dragTimer = setTimeout(() => { if (isMouseDown) { isDragging = true; element.classList.add('dragging'); } }, 150); if (isTouch) { e.preventDefault(); } } // 鼠标/触摸移动 - 绑定到元素上 element.addEventListener('mousemove', handleMove); element.addEventListener('touchmove', handleMove, { passive: false }); function handleMove(e) { // 必须按住鼠标/手指才能拖动 if (!isMouseDown) return; if (!selectedInput || editingInput === element) 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 moveDistance = Math.sqrt( Math.pow(clientX - clickStartX, 2) + Math.pow(clientY - clickStartY, 2) ); if (moveDistance > 5) { hasMoved = true; if (dragTimer) { clearTimeout(dragTimer); dragTimer = null; } if (!isDragging) { isDragging = true; element.classList.add('dragging'); } } if (isDragging) { const dx = clientX - startX; const dy = clientY - startY; element.style.left = (startLeft + dx) + 'px'; element.style.top = (startTop + dy) + 'px'; if (isTouch) { e.preventDefault(); } } } // 鼠标/触摸释放 - 绑定到元素上 element.addEventListener('mouseup', handleEnd); element.addEventListener('touchend', handleEnd); // 鼠标离开元素时也结束拖拽 element.addEventListener('mouseleave', (e) => { if (isMouseDown) { handleEnd(e); } }); function handleEnd(e) { // 标记鼠标/手指释放 isMouseDown = false; // 清除拖拽定时器 if (dragTimer) { clearTimeout(dragTimer); dragTimer = null; } const clickDuration = Date.now() - clickStartTime; // 如果是快速点击且未移动,判定为单击(仅选中,不编辑) if (!hasMoved && clickDuration < 200 && !isDragging) { // 单击仅选中,不进入编辑模式 if (e && e.preventDefault) { e.preventDefault(); } } // 立即结束拖拽状态 if (isDragging) { isDragging = false; element.classList.remove('dragging'); } } // 双击进入编辑模式 element.addEventListener('dblclick', (e) => { e.stopPropagation(); startEdit(element); }); // 触摸双击检测 let lastTapTime = 0; element.addEventListener('touchend', (e) => { const currentTime = Date.now(); if (currentTime - lastTapTime < 300) { // 双击检测 e.preventDefault(); e.stopPropagation(); startEdit(element); } lastTapTime = currentTime; }); } // 选中输入框 function selectInput(element) { // 如果有其他输入框在编辑,先退出编辑 if (editingInput && editingInput !== element) { endEdit(editingInput); } // 取消之前的选中 if (selectedInput && selectedInput !== element) { selectedInput.classList.remove('selected'); } // 选中当前输入框 selectedInput = element; element.classList.add('selected'); element.classList.remove('editing'); element.contentEditable = 'false'; } // 开始编辑 function startEdit(element) { if (editingInput && editingInput !== element) { endEdit(editingInput); } editingInput = element; selectedInput = element; element.classList.add('editing'); element.classList.remove('selected'); element.contentEditable = 'true'; element.focus(); // 光标定位到末尾 const range = document.createRange(); const sel = window.getSelection(); range.selectNodeContents(element); range.collapse(false); sel.removeAllRanges(); sel.addRange(range); // 移动端显示键盘 if ('virtualKeyboard' in navigator) { navigator.virtualKeyboard.show(); } } // 结束编辑 function endEdit(element) { element.classList.remove('editing'); element.contentEditable = 'false'; element.blur(); editingInput = null; } // 取消所有选中 function deselectAll() { if (editingInput) { endEdit(editingInput); } if (selectedInput) { selectedInput.classList.remove('selected'); selectedInput = null; } } // 初始化长按保存 function initLongPressSave() { canvasArea.addEventListener('touchstart', (e) => { // 如果触摸的是输入框,不触发长按保存 if (e.target.closest('.text-input')) { return; } longPressTimer = setTimeout(() => { // 震动反馈 if (navigator.vibrate) { navigator.vibrate(50); } // 保存图片 saveImage(); }, 800); }, { passive: true }); canvasArea.addEventListener('touchend', () => { if (longPressTimer) { clearTimeout(longPressTimer); longPressTimer = null; } }); canvasArea.addEventListener('touchmove', () => { if (longPressTimer) { clearTimeout(longPressTimer); longPressTimer = null; } }); } // 初始化右键菜单 function initContextMenu() { // 禁用默认右键菜单 canvasArea.addEventListener('contextmenu', (e) => { // 如果右键点击的是输入框,显示默认菜单 if (e.target.closest('.text-input')) { return; } e.preventDefault(); showContextMenu(e.clientX, e.clientY); }); // 点击其他地方关闭菜单 document.addEventListener('click', () => { hideContextMenu(); }); } // 显示右键菜单 function showContextMenu(x, y) { hideContextMenu(); const menu = document.createElement('div'); menu.className = 'context-menu'; menu.id = 'contextMenu'; menu.style.left = x + 'px'; menu.style.top = y + 'px'; // 复制到剪贴板选项 const copyItem = document.createElement('div'); copyItem.className = 'context-menu-item'; copyItem.textContent = '复制图片'; copyItem.addEventListener('click', async () => { await copyImageToClipboard(); hideContextMenu(); }); // 保存图片选项 const saveItem = document.createElement('div'); saveItem.className = 'context-menu-item'; saveItem.textContent = '保存图片'; saveItem.addEventListener('click', () => { saveImage(); hideContextMenu(); }); menu.appendChild(copyItem); menu.appendChild(saveItem); document.body.appendChild(menu); } // 隐藏右键菜单 function hideContextMenu() { const existingMenu = document.getElementById('contextMenu'); if (existingMenu) { existingMenu.remove(); } } // 复制图片到剪贴板 async function copyImageToClipboard() { if (!currentImage) { alert('请先添加背景图片'); return; } try { // 取消所有选中和编辑状态 deselectAll(); const canvas = await generateCanvas(); const blob = await new Promise(resolve => canvas.toBlob(resolve, 'image/png')); await navigator.clipboard.write([ new ClipboardItem({ 'image/png': blob }) ]); alert('图片已复制到剪贴板'); } catch (err) { console.error('复制失败:', err); alert('复制失败,请尝试保存图片'); } } // 生成画布(预览与保存样式一致) async function generateCanvas() { const canvas = exportCanvas; const ctx = canvas.getContext('2d'); // 获取容器实际尺寸 const containerWidth = imageContainer.clientWidth; const containerHeight = imageContainer.clientHeight; // 设置画布尺寸与容器一致 canvas.width = containerWidth; canvas.height = containerHeight; // 绘制白色背景 ctx.fillStyle = '#fff'; ctx.fillRect(0, 0, canvas.width, canvas.height); // 如果有背景图,绘制背景图(保持cover效果) if (currentImage) { drawImageCover(ctx, currentImage, 0, 0, containerWidth, containerHeight); } // 绘制文字(保持与预览完全一致的位置和样式) drawTextOnCanvas(ctx, input1, containerWidth, containerHeight); drawTextOnCanvas(ctx, input2, containerWidth, containerHeight); return canvas; } // 绘制图片保持cover效果(与CSS object-fit: cover一致) function drawImageCover(ctx, img, x, y, width, height) { const imgRatio = img.naturalWidth / img.naturalHeight; const containerRatio = width / height; let drawWidth, drawHeight, drawX, drawY; if (imgRatio > containerRatio) { // 图片更宽,以高度为基准 drawHeight = height; drawWidth = height * imgRatio; drawX = x + (width - drawWidth) / 2; drawY = y; } else { // 图片更高,以宽度为基准 drawWidth = width; drawHeight = width / imgRatio; drawX = x; drawY = y + (height - drawHeight) / 2; } ctx.drawImage(img, drawX, drawY, drawWidth, drawHeight); } // 在画布上绘制文字(保持与预览完全一致) function drawTextOnCanvas(ctx, inputElement, containerWidth, containerHeight) { const text = inputElement.textContent; if (!text) return; const style = window.getComputedStyle(inputElement); // 获取输入框位置(相对于容器) const inputLeft = inputElement.offsetLeft; const inputTop = inputElement.offsetTop; // 设置字体样式(与预览完全一致) const fontSize = parseInt(style.fontSize); ctx.font = `${style.fontWeight} ${fontSize}px ${style.fontFamily}`; ctx.fillStyle = style.color; ctx.textBaseline = 'top'; // 绘制文字(保持与预览完全一致的位置) ctx.fillText(text, inputLeft, inputTop); } // 保存图片 async function saveImage() { if (!currentImage) { alert('请先添加背景图片'); return; } try { const canvas = await generateCanvas(); // 下载图片 const link = document.createElement('a'); link.download = '邀请函_' + Date.now() + '.png'; link.href = canvas.toDataURL('image/png'); link.click(); } catch (err) { console.error('保存失败:', err); alert('保存失败,请重试'); } } })();