From 1a43199ca09d54e2a85da7be4bb387ea17c10113 Mon Sep 17 00:00:00 2001 From: Sakurasan <26715255+Sakurasan@users.noreply.github.com> Date: Tue, 29 Sep 2026 02:27:42 +0800 Subject: [PATCH] =?UTF-8?q?=E9=87=8D=E5=86=99=E6=89=8B=E6=9C=BA=E5=9B=BE?= =?UTF-8?q?=E7=89=87=E9=A2=84=E8=A7=88=EF=BC=88=E4=B8=8D=E5=8F=82=E8=80=83?= =?UTF-8?q?=E6=97=A7=E5=AE=9E=E7=8E=B0=EF=BC=89=EF=BC=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 真凶补刀:lightbox.js 状态模块里一直有 body.style.overflow='hidden'——上次撤销只撤了组件没撤它,撑屏从未消失。现在彻底移除,注释写明绝不可再碰 body inline overflow(会覆盖 overflow-x: clip) - 滚动封锁改由遮罩自身 @wheel/@touchmove.prevent 拦截:事件不落页面,背景滚不动、滚动位置保留 - 图片尺寸全走百分比(遮罩即视口),不再引入 vw/vh/dvh - touch-action:none 手势归遮罩:滑动翻页(位移阈值判定)、屏蔽双击缩放/长按菜单/下拉刷新 - 手机断点:箭头贴边缩小、关闭钮/页码走 safe-area、图片全宽无圆角 - Transition 加 :duration 兜底,防 transitionend 不触发时透明遮罩残留吃点击 --- frontend/src/components/ImgLightbox.vue | 104 ++++++++++++++++-------- frontend/src/lightbox.js | 8 +- 2 files changed, 77 insertions(+), 35 deletions(-) diff --git a/frontend/src/components/ImgLightbox.vue b/frontend/src/components/ImgLightbox.vue index 7c34c3f..a1b9a82 100644 --- a/frontend/src/components/ImgLightbox.vue +++ b/frontend/src/components/ImgLightbox.vue @@ -2,41 +2,70 @@ import { onBeforeUnmount, watch } from 'vue' import { lightboxState as st, closeLightbox, stepLightbox } from '../lightbox' -// 图片预览层:App.vue 全局挂一次。铺满全屏的暗底 + 居中大图, -// 多图时左右箭头 / 方向键切换,角标显示第几张;Esc / 点空白收起。 +// 图片预览层(手机重做版):App.vue 全局挂一次。 +// +// 手机上出过两次事故,这次的实现原则: +// 1. 不碰 body 的 overflow——之前用 inline overflow 锁背景,把样式表的 +// overflow-x: clip 覆盖成 hidden,横向从「裁剪」变「可滚动容器」, +// 整页被拖宽。现在滚动封锁靠遮罩自己的 @wheel/@touchmove.prevent: +// 事件不落到页面里,背景自然滚不动,滚动位置也天然保留。 +// 2. 图片尺寸全部用百分比(max-width/height: 100%),不引入 vw/vh/dvh—— +// 这些单位在全面屏 URL 栏收展时各有各的坑,遮罩本身就是视口大小。 +// 3. 手势全归遮罩:touch-action: none + touchend 位移判滑动翻页, +// 双击缩放/长按菜单/下拉刷新这些浏览器手势都不会来捣乱。 +// 4. 桌面能力不变:Esc / 方向键 / 点空白关闭 / 悬停箭头。 function onKey(e) { if (e.key === 'Escape') closeLightbox() if (e.key === 'ArrowLeft') stepLightbox(-1) if (e.key === 'ArrowRight') stepLightbox(1) } + +// 吸顶栏带 backdrop-filter,真机上其合成层会盖在遮罩之上 +// (z-index 更高也拦不住)——开预览时整个藏掉 watch( () => st.open, (v) => { - if (v) { - window.addEventListener('keydown', onKey) - // 吸顶栏带 backdrop-filter,真机上其合成层会盖在遮罩之上 - // (遮罩 z-index 更高也拦不住)——开预览时整个藏掉 - document.body.classList.add('lb-open') - } else { - window.removeEventListener('keydown', onKey) - document.body.classList.remove('lb-open') - } + if (v) window.addEventListener('keydown', onKey) + else window.removeEventListener('keydown', onKey) + document.body.classList.toggle('lb-open', v) } ) -onBeforeUnmount(() => window.removeEventListener('keydown', onKey)) +onBeforeUnmount(() => { + window.removeEventListener('keydown', onKey) + document.body.classList.remove('lb-open') +}) + +// 滑动翻页:横向位移超 44px 且明显大于纵向才算一次滑动 +let touchX = 0 +let touchY = 0 +function onTouchStart(e) { + touchX = e.touches[0].clientX + touchY = e.touches[0].clientY +} +function onTouchEnd(e) { + if (st.list.length < 2) return + const dx = e.changedTouches[0].clientX - touchX + const dy = e.changedTouches[0].clientY - touchY + if (Math.abs(dx) > 44 && Math.abs(dx) > Math.abs(dy) * 1.4) { + stepLightbox(dx < 0 ? 1 : -1) + } +}