diff --git a/PROGRESS.md b/PROGRESS.md index 72523c3..902aacf 100644 --- a/PROGRESS.md +++ b/PROGRESS.md @@ -21,6 +21,11 @@ ## 已完成关键能力 +### 2026-06-15 / 图片预览:左键拖动平移 + 光标语义改正 + +- 光标:100% 时改回普通箭头(原 `zoom-in` 放大镜误导 —— 左键不缩放,缩放是 Ctrl+滚轮);放大后改 `grab`、拖动中 `grabbing`,贴合"可拖"语义。 +- 左键拖动平移:放大态下 mousedown 记起点 + body 滚动位,mousemove 改 `bodyEl.scrollLeft/Top` 平移看局部(替代拖滚动条);`img.draggable=false` 关原生 ghost 拖拽。document 上的 move/up 监听存 `z._onMove/_onUp`,`_clearZoom` 时移除避免泄漏。bump 0.12.12 → 0.12.13。 + ### 2026-06-15 / 文件预览缩放加固 + 双击复位提示 - 图片 load 完即量基准尺寸(`_captureBase`,免首次缩放时还没渲染量到 0px 导致塌成 0);基准未量到时本次缩放跳过不破坏;双击复位时徽标显式提示「已复位 · 100%」(停留 1.4s)。bump 0.12.11 → 0.12.12。 diff --git a/core/__init__.py b/core/__init__.py index eee5419..bec3845 100644 --- a/core/__init__.py +++ b/core/__init__.py @@ -1,3 +1,3 @@ # zcbot 版本号单一事实源:web/app.py 的 FastAPI version、/healthz 返回、前端展示都引这里。 # 改版本只动这一行。 -__version__ = "0.12.12" +__version__ = "0.12.13" diff --git a/web/static/js/preview.js b/web/static/js/preview.js index 4a9b2c5..fe5bef9 100644 --- a/web/static/js/preview.js +++ b/web/static/js/preview.js @@ -84,7 +84,7 @@ function _applyZoom(z, hint) { img.style.maxHeight = ""; img.style.width = ""; img.style.height = ""; - img.style.cursor = "zoom-in"; + img.style.cursor = ""; // 100%:普通箭头(左键不缩放,放大镜会误导) } else { // 以 scale=1 时的"贴合显示"尺寸为基准,给显式像素尺寸。 // 不用 CSS zoom:图片是带 max-width/height:100% 的 flex item,zoom 放大后会被 @@ -94,7 +94,7 @@ function _applyZoom(z, hint) { img.style.maxHeight = "none"; img.style.width = Math.round(z.baseW * z.scale) + "px"; img.style.height = Math.round(z.baseH * z.scale) + "px"; - img.style.cursor = "zoom-out"; + img.style.cursor = "grab"; // 放大后可左键拖动平移 } z.badge.textContent = hint || (Math.round(z.scale * 100) + "%"); z.badge.classList.add("show"); @@ -111,7 +111,7 @@ function _makeImageZoomable(bodyEl, img) { card.appendChild(badge); const z = { img, scale: 1, badge, timer: null, baseW: 0, baseH: 0 }; _zoomState.set(bodyEl, z); - img.style.cursor = "zoom-in"; + img.draggable = false; // 关掉浏览器原生图片拖拽(否则拖出 ghost image) // 图片一加载完就量好贴合尺寸做基准,避免首次缩放时还没渲染量到 0 if (img.complete) _captureBase(z); else img.addEventListener("load", () => _captureBase(z), { once: true }); @@ -120,12 +120,37 @@ function _makeImageZoomable(bodyEl, img) { z.scale = 1; _applyZoom(z, "已复位 · 100%"); }); + + // 放大后左键拖动平移:本质是改 body 的滚动位置(图片此时已撑大、body 可滚) + let dragging = false, sx = 0, sy = 0, sl = 0, st = 0; + img.addEventListener("mousedown", (e) => { + if (e.button !== 0 || z.scale === 1) return; // 仅左键、仅放大态 + e.preventDefault(); + dragging = true; + sx = e.clientX; sy = e.clientY; + sl = bodyEl.scrollLeft; st = bodyEl.scrollTop; + img.style.cursor = "grabbing"; + }); + z._onMove = (e) => { + if (!dragging) return; + bodyEl.scrollLeft = sl - (e.clientX - sx); + bodyEl.scrollTop = st - (e.clientY - sy); + }; + z._onUp = () => { + if (!dragging) return; + dragging = false; + img.style.cursor = z.scale === 1 ? "" : "grab"; + }; + document.addEventListener("mousemove", z._onMove); + document.addEventListener("mouseup", z._onUp); } function _clearZoom(bodyEl) { const z = _zoomState.get(bodyEl); if (!z) return; if (z.timer) clearTimeout(z.timer); + if (z._onMove) document.removeEventListener("mousemove", z._onMove); + if (z._onUp) document.removeEventListener("mouseup", z._onUp); z.badge.remove(); _zoomState.delete(bodyEl); }