From 504ac42159f504808a735ec6d43586f90de71810 Mon Sep 17 00:00:00 2001 From: Zhukj <3262118957> Date: Thu, 16 Apr 2026 14:26:35 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E4=BC=98=E5=8C=96=E6=96=87?= =?UTF-8?q?=E6=9C=AC=E7=BC=96=E8=BE=91=E5=99=A8=E5=B7=A5=E5=85=B7=E6=A0=8F?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=EF=BC=8C=E5=AE=9E=E7=8E=B0=E6=96=87=E5=AD=97?= =?UTF-8?q?=E6=A0=B7=E5=BC=8F=E5=AE=9E=E6=97=B6=E4=BF=AE=E6=94=B9=E4=B8=8E?= =?UTF-8?q?=E5=8F=8C=E7=AB=AF=E6=98=BE=E7=A4=BA=E9=80=82=E9=85=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 9 +++++ css/style.css | 2 ++ js/script.js | 97 +++++++++++++++++++++++++++++---------------------- 3 files changed, 67 insertions(+), 41 deletions(-) diff --git a/README.md b/README.md index fc57d19..a2e2553 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,15 @@ 8. 打开项目时预览区域在屏幕居中显示,选择图片后图片在预览区居左显示,将底部请点击上方「添加图片」按钮上传背景图文字移到背景图预览白板提示中 9. 背景图选择后居左显示,图片原比例保存不变,可以左右滑动调节位置不能小于预览区宽度,预览区与图片合并到同一个图层中,不独立存在,以图片左右为边缘不溢出不拉伸,保持图片比例不变 10. 输入框与背景图完全隔离:输入框的文字编辑、拖动、内容溢出,仅改变输入框自身的位置/大小,不带动背景图偏移,不撑大预览容器,背景图的滑动调整,不影响输入框的位置、编辑状态,操作互不干扰 +11. 添加图片尺寸不一致时可以左右滑动图片调整位置,并且图片反转拖拽 +12. 电脑端右键保存/复制/预览图片,手机端长按屏幕弹窗保存/复制/预览图片,可预览 +13. 顶部工具栏下方16px的位置,固定放置文本编辑器工具栏,加粗,斜体,字体颜色,字体,字号,可编辑文字样式 +14. 双击输入框进入编辑状态后,电脑端可左键滑动选的中字体,手机端可双击第二下不松滑动选的中字体 + + + + + ## 需要注意的点 diff --git a/css/style.css b/css/style.css index 047cab6..c685198 100644 --- a/css/style.css +++ b/css/style.css @@ -263,6 +263,8 @@ html, body { cursor: text; -webkit-user-select: text; user-select: text; + -webkit-touch-callout: default; + pointer-events: auto; } /* 拖拽状态 */ diff --git a/js/script.js b/js/script.js index 92f9efa..293e2aa 100644 --- a/js/script.js +++ b/js/script.js @@ -62,12 +62,13 @@ // 点击空白区域取消选中 document.addEventListener('click', (e) => { - // 只有点击真正的空白区域(不是输入框,不是图片容器)才取消选中 + // 只有点击真正的空白区域(不是输入框,不是图片容器,不是工具栏)才取消选中 const isTextInput = e.target.closest('.text-input'); const isImageContainer = e.target.closest('.image-container'); const isToolbar = e.target.closest('.toolbar'); + const isTextEditorToolbar = e.target.closest('.text-editor-toolbar'); - if (!isTextInput && !isImageContainer && !isToolbar) { + if (!isTextInput && !isImageContainer && !isToolbar && !isTextEditorToolbar) { deselectAll(); } }); @@ -275,6 +276,12 @@ function handleStart(e) { const isTouch = e.type === 'touchstart'; + + // 如果正在编辑此输入框,不处理拖拽事件,让浏览器处理文本选择 + if (editingInput === element) { + return; + } + const clientX = isTouch ? e.touches[0].clientX : e.clientX; const clientY = isTouch ? e.touches[0].clientY : e.clientY; @@ -285,11 +292,6 @@ hasMoved = false; isDragging = false; - // 如果正在编辑此输入框,不处理 - if (editingInput === element) { - return; - } - // 选中此输入框 selectInput(element); @@ -306,10 +308,6 @@ } }, 150); - if (isTouch) { - e.preventDefault(); - } - // 阻止事件冒泡,避免触发图片滑动 e.stopPropagation(); } @@ -319,8 +317,13 @@ element.addEventListener('touchmove', handleMove, { passive: false }); function handleMove(e) { + // 如果正在编辑,不拦截事件,让浏览器处理文本选择 + if (editingInput === element) { + return; + } + if (!isMouseDown) return; - if (!selectedInput || editingInput === element) return; + if (!selectedInput) return; const isTouch = e.type === 'touchmove'; const clientX = isTouch ? e.touches[0].clientX : e.clientX; @@ -348,16 +351,10 @@ const dy = clientY - startY; element.style.left = (startLeft + dx) + 'px'; element.style.top = (startTop + dy) + 'px'; - if (isTouch) { - e.preventDefault(); - } } - // 阻止事件冒泡和默认行为,确保输入框拖动完全独立,不影响背景图 + // 阻止事件冒泡,避免触发图片滑动 e.stopPropagation(); - if (isTouch) { - e.preventDefault(); - } } // 鼠标/触摸释放 @@ -389,18 +386,17 @@ } } - // 双击进入编辑模式 + // 双击进入编辑模式(电脑端和移动端通用) 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); } @@ -436,18 +432,22 @@ element.classList.add('editing'); element.classList.remove('selected'); element.contentEditable = 'true'; - element.focus(); // 更新工具栏状态 updateToolbarState(element); - // 光标定位到末尾 - const range = document.createRange(); - const sel = window.getSelection(); - range.selectNodeContents(element); - range.collapse(false); - sel.removeAllRanges(); - sel.addRange(range); + // 如果没有选中文本,则定位光标到末尾 + setTimeout(() => { + const sel = window.getSelection(); + if (sel.isCollapsed) { + const range = document.createRange(); + range.selectNodeContents(element); + range.collapse(false); + sel.removeAllRanges(); + sel.addRange(range); + } + element.focus(); + }, 10); if ('virtualKeyboard' in navigator) { navigator.virtualKeyboard.show(); @@ -567,31 +567,46 @@ // 加粗按钮 boldBtn.addEventListener('click', () => { if (!editingInput) return; - const isBold = boldBtn.classList.contains('active'); - if (isBold) { - editingInput.style.fontWeight = 'normal'; + const selection = window.getSelection(); + if (selection.rangeCount > 0 && !selection.isCollapsed) { + document.execCommand('bold', false, null); } else { - editingInput.style.fontWeight = 'bold'; + const isBold = boldBtn.classList.contains('active'); + if (isBold) { + editingInput.style.fontWeight = 'normal'; + } else { + editingInput.style.fontWeight = 'bold'; + } + boldBtn.classList.toggle('active'); } - boldBtn.classList.toggle('active'); }); // 斜体按钮 italicBtn.addEventListener('click', () => { if (!editingInput) return; - const isItalic = italicBtn.classList.contains('active'); - if (isItalic) { - editingInput.style.fontStyle = 'normal'; + const selection = window.getSelection(); + if (selection.rangeCount > 0 && !selection.isCollapsed) { + document.execCommand('italic', false, null); } else { - editingInput.style.fontStyle = 'italic'; + const isItalic = italicBtn.classList.contains('active'); + if (isItalic) { + editingInput.style.fontStyle = 'normal'; + } else { + editingInput.style.fontStyle = 'italic'; + } + italicBtn.classList.toggle('active'); } - italicBtn.classList.toggle('active'); }); // 颜色选择器 colorPicker.addEventListener('input', () => { if (!editingInput) return; - editingInput.style.color = colorPicker.value; + const selection = window.getSelection(); + if (selection.rangeCount > 0 && !selection.isCollapsed) { + document.execCommand('foreColor', false, colorPicker.value); + } else { + editingInput.style.color = colorPicker.value; + } }); // 字体选择器